media_stream_devices_controller.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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.
4
5#include "chrome/browser/media/media_stream_devices_controller.h"
6
7#include "base/command_line.h"
8#include "base/prefs/pref_service.h"
9#include "base/values.h"
10#include "chrome/browser/content_settings/content_settings_provider.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/prefs/scoped_user_pref_update.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/content_settings.h"
20#include "chrome/common/pref_names.h"
21#include "components/user_prefs/pref_registry_syncable.h"
22#include "content/public/browser/browser_thread.h"
23#include "content/public/common/media_stream_request.h"
24
25using content::BrowserThread;
26
27namespace {
28
29bool HasAnyAvailableDevice() {
30  const content::MediaStreamDevices& audio_devices =
31      MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices();
32  const content::MediaStreamDevices& video_devices =
33      MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices();
34
35  return !audio_devices.empty() || !video_devices.empty();
36};
37
38}  // namespace
39
40MediaStreamDevicesController::MediaStreamDevicesController(
41    content::WebContents* web_contents,
42    const content::MediaStreamRequest& request,
43    const content::MediaResponseCallback& callback)
44    : web_contents_(web_contents),
45      request_(request),
46      callback_(callback),
47      microphone_requested_(
48          request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE),
49      webcam_requested_(
50          request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) {
51  profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
52  content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
53
54  // Don't call GetDevicePolicy from the initializer list since the
55  // implementation depends on member variables.
56  if (microphone_requested_ &&
57      GetDevicePolicy(prefs::kAudioCaptureAllowed) == ALWAYS_DENY) {
58    microphone_requested_ = false;
59  }
60
61  if (webcam_requested_ &&
62      GetDevicePolicy(prefs::kVideoCaptureAllowed) == ALWAYS_DENY) {
63    webcam_requested_ = false;
64  }
65}
66
67MediaStreamDevicesController::~MediaStreamDevicesController() {}
68
69// static
70void MediaStreamDevicesController::RegisterUserPrefs(
71    user_prefs::PrefRegistrySyncable* prefs) {
72  prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed,
73                             true,
74                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
75  prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed,
76                             true,
77                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
78}
79
80
81bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() {
82  // If this is a no UI check for policies only go straight to accept - policy
83  // check will be done automatically on the way.
84  if (request_.request_type == content::MEDIA_OPEN_DEVICE) {
85    Accept(false);
86    return true;
87  }
88
89  // Tab capture is allowed for extensions only and infobar is not shown for
90  // extensions.
91  if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE ||
92      request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) {
93    Deny(false);
94    return true;
95  }
96
97  // Deny the request if the security origin is empty, this happens with
98  // file access without |--allow-file-access-from-files| flag.
99  if (request_.security_origin.is_empty()) {
100    Deny(false);
101    return true;
102  }
103
104  // Deny the request if there is no device attached to the OS.
105  if (!HasAnyAvailableDevice()) {
106    Deny(false);
107    return true;
108  }
109
110  // Check if any allow exception has been made for this request.
111  if (IsRequestAllowedByDefault()) {
112    Accept(false);
113    return true;
114  }
115
116  // Check if any block exception has been made for this request.
117  if (IsRequestBlockedByDefault()) {
118    Deny(false);
119    return true;
120  }
121
122  // Check if the media default setting is set to block.
123  if (IsDefaultMediaAccessBlocked()) {
124    Deny(false);
125    return true;
126  }
127
128  // Show the infobar.
129  return false;
130}
131
132const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const {
133  return request_.security_origin.spec();
134}
135
136void MediaStreamDevicesController::Accept(bool update_content_setting) {
137  if (content_settings_)
138    content_settings_->OnMediaStreamAllowed();
139
140  // Get the default devices for the request.
141  content::MediaStreamDevices devices;
142  if (microphone_requested_ || webcam_requested_) {
143    switch (request_.request_type) {
144      case content::MEDIA_OPEN_DEVICE:
145        // For open device request pick the desired device or fall back to the
146        // first available of the given type.
147        MediaCaptureDevicesDispatcher::GetInstance()->GetRequestedDevice(
148            request_.requested_device_id,
149            microphone_requested_,
150            webcam_requested_,
151            &devices);
152        break;
153      case content::MEDIA_DEVICE_ACCESS:
154      case content::MEDIA_GENERATE_STREAM:
155      case content::MEDIA_ENUMERATE_DEVICES:
156        // Get the default devices for the request.
157        MediaCaptureDevicesDispatcher::GetInstance()->
158            GetDefaultDevicesForProfile(profile_,
159                                        microphone_requested_,
160                                        webcam_requested_,
161                                        &devices);
162        break;
163    }
164
165    if (update_content_setting && IsSchemeSecure() && !devices.empty())
166      SetPermission(true);
167  }
168
169  scoped_ptr<content::MediaStreamUI> ui;
170  if (!devices.empty()) {
171    ui = MediaCaptureDevicesDispatcher::GetInstance()->
172        GetMediaStreamCaptureIndicator()->RegisterMediaStream(
173            web_contents_, devices);
174  }
175  callback_.Run(devices, ui.Pass());
176}
177
178void MediaStreamDevicesController::Deny(bool update_content_setting) {
179  // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the
180  // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and
181  // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA.
182  if (content_settings_) {
183    content_settings_->OnContentBlocked(CONTENT_SETTINGS_TYPE_MEDIASTREAM,
184                                        std::string());
185  }
186
187  if (update_content_setting)
188    SetPermission(false);
189
190  callback_.Run(content::MediaStreamDevices(),
191                scoped_ptr<content::MediaStreamUI>());
192}
193
194MediaStreamDevicesController::DevicePolicy
195MediaStreamDevicesController::GetDevicePolicy(const char* policy_name) const {
196  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
197
198  PrefService* prefs = profile_->GetPrefs();
199  if (!prefs->IsManagedPreference(policy_name))
200    return POLICY_NOT_SET;
201
202  return prefs->GetBoolean(policy_name) ? ALWAYS_ALLOW : ALWAYS_DENY;
203}
204
205bool MediaStreamDevicesController::IsRequestAllowedByDefault() const {
206  // The request from internal objects like chrome://URLs is always allowed.
207  if (ShouldAlwaysAllowOrigin())
208    return true;
209
210  struct {
211    bool has_capability;
212    const char* policy_name;
213    ContentSettingsType settings_type;
214  } device_checks[] = {
215    { microphone_requested_, prefs::kAudioCaptureAllowed,
216      CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC },
217    { webcam_requested_, prefs::kVideoCaptureAllowed,
218      CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA },
219  };
220
221  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) {
222    if (!device_checks[i].has_capability)
223      continue;
224
225    DevicePolicy policy = GetDevicePolicy(device_checks[i].policy_name);
226    if (policy == ALWAYS_DENY ||
227        (policy == POLICY_NOT_SET &&
228         profile_->GetHostContentSettingsMap()->GetContentSetting(
229            request_.security_origin, request_.security_origin,
230            device_checks[i].settings_type, NO_RESOURCE_IDENTIFIER) !=
231         CONTENT_SETTING_ALLOW)) {
232      return false;
233    }
234    // If we get here, then either policy is set to ALWAYS_ALLOW or the content
235    // settings allow the request by default.
236  }
237
238  return true;
239}
240
241bool MediaStreamDevicesController::IsRequestBlockedByDefault() const {
242  if (microphone_requested_ &&
243      profile_->GetHostContentSettingsMap()->GetContentSetting(
244          request_.security_origin,
245          request_.security_origin,
246          CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
247          NO_RESOURCE_IDENTIFIER) != CONTENT_SETTING_BLOCK) {
248    return false;
249  }
250
251  if (webcam_requested_ &&
252      profile_->GetHostContentSettingsMap()->GetContentSetting(
253          request_.security_origin,
254          request_.security_origin,
255          CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
256          NO_RESOURCE_IDENTIFIER) != CONTENT_SETTING_BLOCK) {
257    return false;
258  }
259
260  return true;
261}
262
263bool MediaStreamDevicesController::IsDefaultMediaAccessBlocked() const {
264  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
265  // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the
266  // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and
267  // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA.
268  ContentSetting current_setting =
269      profile_->GetHostContentSettingsMap()->GetDefaultContentSetting(
270          CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL);
271  return (current_setting == CONTENT_SETTING_BLOCK);
272}
273
274bool MediaStreamDevicesController::IsSchemeSecure() const {
275  return (request_.security_origin.SchemeIsSecure());
276}
277
278bool MediaStreamDevicesController::ShouldAlwaysAllowOrigin() const {
279  // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the
280  // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and
281  // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA.
282  return profile_->GetHostContentSettingsMap()->ShouldAllowAllContent(
283      request_.security_origin, request_.security_origin,
284      CONTENT_SETTINGS_TYPE_MEDIASTREAM);
285}
286
287void MediaStreamDevicesController::SetPermission(bool allowed) const {
288  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
289#if defined(OS_ANDROID)
290  // We do not support sticky operations on Android yet.
291  return;
292#endif
293  ContentSettingsPattern primary_pattern =
294      ContentSettingsPattern::FromURLNoWildcard(request_.security_origin);
295  // Check the pattern is valid or not. When the request is from a file access,
296  // no exception will be made.
297  if (!primary_pattern.IsValid())
298    return;
299
300  ContentSetting content_setting = allowed ?
301      CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
302  if (microphone_requested_) {
303      profile_->GetHostContentSettingsMap()->SetContentSetting(
304        primary_pattern,
305        ContentSettingsPattern::Wildcard(),
306        CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
307        std::string(),
308        content_setting);
309  }
310  if (webcam_requested_) {
311    profile_->GetHostContentSettingsMap()->SetContentSetting(
312        primary_pattern,
313        ContentSettingsPattern::Wildcard(),
314        CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
315        std::string(),
316        content_setting);
317  }
318}
319