media_stream_devices_controller.cc revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
1337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne// Use of this source code is governed by a BSD-style license that can be
3340a133d08c3c1ec9a0e03669ff692185c520e8cLode Vandevenne// found in the LICENSE file.
4337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne
5337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/media/media_stream_devices_controller.h"
6337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne
7337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "base/command_line.h"
8337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "base/prefs/pref_service.h"
9337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "base/values.h"
10337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/content_settings/content_settings_provider.h"
11337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/content_settings/host_content_settings_map.h"
12337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/content_settings/tab_specific_content_settings.h"
13337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/media/media_capture_devices_dispatcher.h"
14337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/media/media_stream_capture_indicator.h"
15337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/prefs/scoped_user_pref_update.h"
16337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/profiles/profile.h"
17337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/ui/browser.h"
18337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/common/chrome_switches.h"
19337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/common/content_settings.h"
20337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/common/content_settings_pattern.h"
21337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/common/pref_names.h"
22337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "components/user_prefs/pref_registry_syncable.h"
23337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "content/public/browser/browser_thread.h"
24337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "content/public/common/media_stream_request.h"
25337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "extensions/common/constants.h"
26337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne
27337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#if defined(OS_CHROMEOS)
28337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#include "chrome/browser/chromeos/login/user_manager.h"
29337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne#endif
30337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne
31337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenneusing content::BrowserThread;
32337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne
33337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevennenamespace {
34337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne
35337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevennebool HasAnyAvailableDevice() {
36337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne  const content::MediaStreamDevices& audio_devices =
37337d27f25ef15a6cf34fef2acd0613fddc411cb1Lode Vandevenne      MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices();
38  const content::MediaStreamDevices& video_devices =
39      MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices();
40
41  return !audio_devices.empty() || !video_devices.empty();
42}
43
44bool IsInKioskMode() {
45  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode))
46    return true;
47
48#if defined(OS_CHROMEOS)
49  const chromeos::UserManager* user_manager = chromeos::UserManager::Get();
50  return user_manager && user_manager->IsLoggedInAsKioskApp();
51#else
52  return false;
53#endif
54}
55
56}  // namespace
57
58MediaStreamDevicesController::MediaStreamDevicesController(
59    content::WebContents* web_contents,
60    const content::MediaStreamRequest& request,
61    const content::MediaResponseCallback& callback)
62    : web_contents_(web_contents),
63      request_(request),
64      callback_(callback) {
65  profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
66  content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
67
68  // For MEDIA_OPEN_DEVICE requests (Pepper) we always request both webcam
69  // and microphone to avoid popping two infobars.
70  // We start with setting the requested media type to allowed or blocked
71  // depending on the policy. If not blocked by policy it may be blocked later
72  // in the two remaining filtering steps (by user setting or by user when
73  // clicking the infobar).
74  // TODO(grunell): It's not the nicest solution to let the MEDIA_OPEN_DEVICE
75  // case take a ride on the MEDIA_DEVICE_*_CAPTURE permission. Should be fixed.
76  if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
77      request.request_type == content::MEDIA_OPEN_DEVICE) {
78    if (GetDevicePolicy(prefs::kAudioCaptureAllowed,
79                        prefs::kAudioCaptureAllowedUrls) == ALWAYS_DENY) {
80      request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE] =
81          MEDIA_BLOCKED_BY_POLICY;
82    } else {
83      request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE] = MEDIA_ALLOWED;
84    }
85  }
86  if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE ||
87      request.request_type == content::MEDIA_OPEN_DEVICE) {
88    if (GetDevicePolicy(prefs::kVideoCaptureAllowed,
89                        prefs::kVideoCaptureAllowedUrls) == ALWAYS_DENY) {
90      request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE] =
91          MEDIA_BLOCKED_BY_POLICY;
92    } else {
93      request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE] = MEDIA_ALLOWED;
94    }
95  }
96}
97
98MediaStreamDevicesController::~MediaStreamDevicesController() {
99  if (!callback_.is_null()) {
100    callback_.Run(content::MediaStreamDevices(),
101                  scoped_ptr<content::MediaStreamUI>());
102  }
103}
104
105// static
106void MediaStreamDevicesController::RegisterProfilePrefs(
107    user_prefs::PrefRegistrySyncable* prefs) {
108  prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed,
109                             true,
110                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
111  prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed,
112                             true,
113                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
114  prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls,
115                          user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
116  prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls,
117                          user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
118}
119
120
121bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() {
122  // Tab capture is allowed for extensions only and infobar is not shown for
123  // extensions.
124  if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE ||
125      request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) {
126    Deny(false);
127    return true;
128  }
129
130  // Deny the request if the security origin is empty, this happens with
131  // file access without |--allow-file-access-from-files| flag.
132  if (request_.security_origin.is_empty()) {
133    Deny(false);
134    return true;
135  }
136
137  // Deny the request if there is no device attached to the OS.
138  if (!HasAnyAvailableDevice()) {
139    Deny(false);
140    return true;
141  }
142
143  // Check if any allow exception has been made for this request.
144  if (IsRequestAllowedByDefault()) {
145    Accept(false);
146    return true;
147  }
148
149  // Filter any parts of the request that have been blocked by default and deny
150  // it if nothing is left to accept.
151  if (FilterBlockedByDefaultDevices() == 0) {
152    Deny(false);
153    return true;
154  }
155
156  // Check if the media default setting is set to block.
157  if (IsDefaultMediaAccessBlocked()) {
158    Deny(false);
159    return true;
160  }
161
162  // Show the infobar.
163  return false;
164}
165
166bool MediaStreamDevicesController::HasAudio() const {
167  return IsDeviceAudioCaptureRequestedAndAllowed();
168}
169
170bool MediaStreamDevicesController::HasVideo() const {
171  return IsDeviceVideoCaptureRequestedAndAllowed();
172}
173
174const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const {
175  return request_.security_origin.spec();
176}
177
178void MediaStreamDevicesController::Accept(bool update_content_setting) {
179  NotifyUIRequestAccepted();
180
181  // Get the default devices for the request.
182  content::MediaStreamDevices devices;
183  bool audio_allowed = IsDeviceAudioCaptureRequestedAndAllowed();
184  bool video_allowed = IsDeviceVideoCaptureRequestedAndAllowed();
185  if (audio_allowed || video_allowed) {
186    switch (request_.request_type) {
187      case content::MEDIA_OPEN_DEVICE: {
188        const content::MediaStreamDevice* device = NULL;
189        // For open device request pick the desired device or fall back to the
190        // first available of the given type.
191        if (request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) {
192          device = MediaCaptureDevicesDispatcher::GetInstance()->
193              GetRequestedAudioDevice(request_.requested_audio_device_id);
194          // TODO(wjia): Confirm this is the intended behavior.
195          if (!device) {
196            device = MediaCaptureDevicesDispatcher::GetInstance()->
197                GetFirstAvailableAudioDevice();
198          }
199        } else if (request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) {
200          // Pepper API opens only one device at a time.
201          device = MediaCaptureDevicesDispatcher::GetInstance()->
202              GetRequestedVideoDevice(request_.requested_video_device_id);
203          // TODO(wjia): Confirm this is the intended behavior.
204          if (!device) {
205            device = MediaCaptureDevicesDispatcher::GetInstance()->
206                GetFirstAvailableVideoDevice();
207          }
208        }
209        if (device)
210          devices.push_back(*device);
211        break;
212      } case content::MEDIA_GENERATE_STREAM: {
213        bool needs_audio_device = audio_allowed;
214        bool needs_video_device = video_allowed;
215
216        // Get the exact audio or video device if an id is specified.
217        if (!request_.requested_audio_device_id.empty()) {
218          const content::MediaStreamDevice* audio_device =
219              MediaCaptureDevicesDispatcher::GetInstance()->
220                  GetRequestedAudioDevice(request_.requested_audio_device_id);
221          if (audio_device) {
222            devices.push_back(*audio_device);
223            needs_audio_device = false;
224          }
225        }
226        if (!request_.requested_video_device_id.empty()) {
227          const content::MediaStreamDevice* video_device =
228              MediaCaptureDevicesDispatcher::GetInstance()->
229                  GetRequestedVideoDevice(request_.requested_video_device_id);
230          if (video_device) {
231            devices.push_back(*video_device);
232            needs_video_device = false;
233          }
234        }
235
236        // If either or both audio and video devices were requested but not
237        // specified by id, get the default devices.
238        if (needs_audio_device || needs_video_device) {
239          MediaCaptureDevicesDispatcher::GetInstance()->
240              GetDefaultDevicesForProfile(profile_,
241                                          needs_audio_device,
242                                          needs_video_device,
243                                          &devices);
244        }
245        break;
246      } case content::MEDIA_DEVICE_ACCESS:
247        // Get the default devices for the request.
248        MediaCaptureDevicesDispatcher::GetInstance()->
249            GetDefaultDevicesForProfile(profile_,
250                                        audio_allowed,
251                                        video_allowed,
252                                        &devices);
253        break;
254      case content::MEDIA_ENUMERATE_DEVICES:
255        // Do nothing.
256        NOTREACHED();
257        break;
258    }
259
260    // TODO(raymes): We currently set the content permission for non-https
261    // websites for Pepper requests as well. This is temporary and should be
262    // removed.
263    if (update_content_setting) {
264      if ((IsSchemeSecure() && !devices.empty()) ||
265          request_.request_type == content::MEDIA_OPEN_DEVICE) {
266        SetPermission(true);
267      }
268    }
269  }
270
271  scoped_ptr<content::MediaStreamUI> ui;
272  if (!devices.empty()) {
273    ui = MediaCaptureDevicesDispatcher::GetInstance()->
274        GetMediaStreamCaptureIndicator()->RegisterMediaStream(
275            web_contents_, devices);
276  }
277  content::MediaResponseCallback cb = callback_;
278  callback_.Reset();
279  cb.Run(devices, ui.Pass());
280}
281
282void MediaStreamDevicesController::Deny(bool update_content_setting) {
283  NotifyUIRequestDenied();
284
285  if (update_content_setting)
286    SetPermission(false);
287
288  content::MediaResponseCallback cb = callback_;
289  callback_.Reset();
290  cb.Run(content::MediaStreamDevices(), scoped_ptr<content::MediaStreamUI>());
291}
292
293MediaStreamDevicesController::DevicePolicy
294MediaStreamDevicesController::GetDevicePolicy(
295    const char* policy_name,
296    const char* whitelist_policy_name) const {
297  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298
299  // If the security origin policy matches a value in the whitelist, allow it.
300  // Otherwise, check the |policy_name| master switch for the default behavior.
301
302  PrefService* prefs = profile_->GetPrefs();
303
304  // TODO(tommi): Remove the kiosk mode check when the whitelist below
305  // is visible in the media exceptions UI.
306  // See discussion here: https://codereview.chromium.org/15738004/
307  if (IsInKioskMode()) {
308    const base::ListValue* list = prefs->GetList(whitelist_policy_name);
309    std::string value;
310    for (size_t i = 0; i < list->GetSize(); ++i) {
311      if (list->GetString(i, &value)) {
312        ContentSettingsPattern pattern =
313            ContentSettingsPattern::FromString(value);
314        if (pattern == ContentSettingsPattern::Wildcard()) {
315          DLOG(WARNING) << "Ignoring wildcard URL pattern: " << value;
316          continue;
317        }
318        DLOG_IF(ERROR, !pattern.IsValid()) << "Invalid URL pattern: " << value;
319        if (pattern.IsValid() && pattern.Matches(request_.security_origin))
320          return ALWAYS_ALLOW;
321      }
322    }
323  }
324
325  // If a match was not found, check if audio capture is otherwise disallowed
326  // or if the user should be prompted.  Setting the policy value to "true"
327  // is equal to not setting it at all, so from hereon out, we will return
328  // either POLICY_NOT_SET (prompt) or ALWAYS_DENY (no prompt, no access).
329  if (!prefs->GetBoolean(policy_name))
330    return ALWAYS_DENY;
331
332  return POLICY_NOT_SET;
333}
334
335bool MediaStreamDevicesController::IsRequestAllowedByDefault() const {
336  // The request from internal objects like chrome://URLs is always allowed.
337  if (ShouldAlwaysAllowOrigin())
338    return true;
339
340  struct {
341    bool has_capability;
342    const char* policy_name;
343    const char* list_policy_name;
344    ContentSettingsType settings_type;
345  } device_checks[] = {
346    { IsDeviceAudioCaptureRequestedAndAllowed(), prefs::kAudioCaptureAllowed,
347      prefs::kAudioCaptureAllowedUrls, CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC },
348    { IsDeviceVideoCaptureRequestedAndAllowed(), prefs::kVideoCaptureAllowed,
349      prefs::kVideoCaptureAllowedUrls,
350      CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA },
351  };
352
353  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) {
354    if (!device_checks[i].has_capability)
355      continue;
356
357    DevicePolicy policy = GetDevicePolicy(device_checks[i].policy_name,
358                                          device_checks[i].list_policy_name);
359
360    if (policy == ALWAYS_DENY)
361      return false;
362
363    if (policy == POLICY_NOT_SET) {
364      // Only load content settings from secure origins unless it is a
365      // content::MEDIA_OPEN_DEVICE (Pepper) request.
366      if (!IsSchemeSecure() &&
367          request_.request_type != content::MEDIA_OPEN_DEVICE) {
368        return false;
369      }
370      if (profile_->GetHostContentSettingsMap()->GetContentSetting(
371              request_.security_origin, request_.security_origin,
372              device_checks[i].settings_type, NO_RESOURCE_IDENTIFIER) !=
373              CONTENT_SETTING_ALLOW) {
374        return false;
375      }
376    }
377    // If we get here, then either policy is set to ALWAYS_ALLOW or the content
378    // settings allow the request by default.
379  }
380
381  return true;
382}
383
384int MediaStreamDevicesController::FilterBlockedByDefaultDevices() {
385  int requested_devices = 0;
386
387  if (IsDeviceAudioCaptureRequestedAndAllowed()) {
388    if (profile_->GetHostContentSettingsMap()->GetContentSetting(
389        request_.security_origin,
390        request_.security_origin,
391        CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
392        NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) {
393      request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE] =
394          MEDIA_BLOCKED_BY_USER_SETTING;
395    } else {
396      ++requested_devices;
397    }
398  }
399
400  if (IsDeviceVideoCaptureRequestedAndAllowed()) {
401    if (profile_->GetHostContentSettingsMap()->GetContentSetting(
402        request_.security_origin,
403        request_.security_origin,
404        CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
405        NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) {
406      request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE] =
407          MEDIA_BLOCKED_BY_USER_SETTING;
408    } else {
409      ++requested_devices;
410    }
411  }
412
413  return requested_devices;
414}
415
416bool MediaStreamDevicesController::IsDefaultMediaAccessBlocked() const {
417  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
418  // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the
419  // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and
420  // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA.
421  ContentSetting current_setting =
422      profile_->GetHostContentSettingsMap()->GetDefaultContentSetting(
423          CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL);
424  return (current_setting == CONTENT_SETTING_BLOCK);
425}
426
427bool MediaStreamDevicesController::IsSchemeSecure() const {
428  return request_.security_origin.SchemeIsSecure() ||
429      request_.security_origin.SchemeIs(extensions::kExtensionScheme);
430}
431
432bool MediaStreamDevicesController::ShouldAlwaysAllowOrigin() const {
433  // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the
434  // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and
435  // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA.
436  return profile_->GetHostContentSettingsMap()->ShouldAllowAllContent(
437      request_.security_origin, request_.security_origin,
438      CONTENT_SETTINGS_TYPE_MEDIASTREAM);
439}
440
441void MediaStreamDevicesController::SetPermission(bool allowed) const {
442  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
443#if defined(OS_ANDROID)
444  // We do not support sticky operations on Android yet.
445  return;
446#endif
447  ContentSettingsPattern primary_pattern =
448      ContentSettingsPattern::FromURLNoWildcard(request_.security_origin);
449  // Check the pattern is valid or not. When the request is from a file access,
450  // no exception will be made.
451  if (!primary_pattern.IsValid())
452    return;
453
454  ContentSetting content_setting = allowed ?
455      CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
456  if (request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE) !=
457      request_permissions_.end()) {
458      profile_->GetHostContentSettingsMap()->SetContentSetting(
459        primary_pattern,
460        ContentSettingsPattern::Wildcard(),
461        CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
462        std::string(),
463        content_setting);
464  }
465  if (request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE) !=
466      request_permissions_.end()) {
467    profile_->GetHostContentSettingsMap()->SetContentSetting(
468        primary_pattern,
469        ContentSettingsPattern::Wildcard(),
470        CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
471        std::string(),
472        content_setting);
473  }
474}
475
476void MediaStreamDevicesController::NotifyUIRequestAccepted() const {
477  if (!content_settings_)
478    return;
479
480  content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
481                                                request_permissions_);
482}
483
484void MediaStreamDevicesController::NotifyUIRequestDenied() {
485  if (!content_settings_)
486    return;
487
488  if (IsDeviceAudioCaptureRequestedAndAllowed()) {
489    request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE] =
490        MEDIA_BLOCKED_BY_USER;
491  }
492  if (IsDeviceVideoCaptureRequestedAndAllowed()) {
493    request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE] =
494        MEDIA_BLOCKED_BY_USER;
495  }
496
497  content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
498                                                request_permissions_);
499}
500
501bool MediaStreamDevicesController::IsDeviceAudioCaptureRequestedAndAllowed()
502    const {
503  MediaStreamTypePermissionMap::const_iterator it =
504      request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE);
505  return it != request_permissions_.end() && it->second == MEDIA_ALLOWED;
506}
507
508bool MediaStreamDevicesController::IsDeviceVideoCaptureRequestedAndAllowed()
509    const {
510  MediaStreamTypePermissionMap::const_iterator it =
511      request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE);
512  return it != request_permissions_.end() && it->second == MEDIA_ALLOWED;
513}
514