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_capture_devices_dispatcher.h"
6
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/prefs/scoped_user_pref_update.h"
11 #include "base/sha1.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
16 #include "chrome/browser/media/audio_stream_indicator.h"
17 #include "chrome/browser/media/desktop_streams_registry.h"
18 #include "chrome/browser/media/media_stream_capture_indicator.h"
19 #include "chrome/browser/media/media_stream_infobar_delegate.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/ui/screen_capture_notification_ui.h"
22 #include "chrome/browser/ui/simple_message_box.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/pref_names.h"
25 #include "components/user_prefs/pref_registry_syncable.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "content/public/browser/desktop_media_id.h"
28 #include "content/public/browser/media_devices_monitor.h"
29 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/notification_source.h"
31 #include "content/public/browser/notification_types.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/common/media_stream_request.h"
34 #include "extensions/common/constants.h"
35 #include "extensions/common/extension.h"
36 #include "grit/generated_resources.h"
37 #include "media/audio/audio_manager_base.h"
38 #include "ui/base/l10n/l10n_util.h"
39
40 #if defined(OS_CHROMEOS)
41 #include "ash/shell.h"
42 #endif // defined(OS_CHROMEOS)
43
44 using content::BrowserThread;
45 using content::MediaStreamDevices;
46
47 namespace {
48
49 // Finds a device in |devices| that has |device_id|, or NULL if not found.
FindDeviceWithId(const content::MediaStreamDevices & devices,const std::string & device_id)50 const content::MediaStreamDevice* FindDeviceWithId(
51 const content::MediaStreamDevices& devices,
52 const std::string& device_id) {
53 content::MediaStreamDevices::const_iterator iter = devices.begin();
54 for (; iter != devices.end(); ++iter) {
55 if (iter->id == device_id) {
56 return &(*iter);
57 }
58 }
59 return NULL;
60 };
61
62 // This is a short-term solution to grant camera and/or microphone access to
63 // extensions:
64 // 1. Virtual keyboard extension.
65 // 2. Google Voice Search Hotword extension.
66 // 3. Flutter gesture recognition extension.
67 // 4. TODO(smus): Airbender experiment 1.
68 // 5. TODO(smus): Airbender experiment 2.
69 // Once http://crbug.com/292856 is fixed, remove this whitelist.
IsMediaRequestWhitelistedForExtension(const extensions::Extension * extension)70 bool IsMediaRequestWhitelistedForExtension(
71 const extensions::Extension* extension) {
72 return extension->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
73 extension->id() == "bepbmhgboaologfdajaanbcjmnhjmhfn" ||
74 extension->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
75 extension->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
76 extension->id() == "nnckehldicaciogcbchegobnafnjkcne";
77 }
78
79 // This is a short-term solution to allow testing of the the Screen Capture API
80 // with Google Hangouts in M27.
81 // TODO(sergeyu): Remove this whitelist as soon as possible.
IsOriginWhitelistedForScreenCapture(const GURL & origin)82 bool IsOriginWhitelistedForScreenCapture(const GURL& origin) {
83 #if defined(OFFICIAL_BUILD)
84 if (// Google Hangouts.
85 (origin.SchemeIs("https") &&
86 EndsWith(origin.spec(), ".talkgadget.google.com/", true)) ||
87 origin.spec() == "https://talkgadget.google.com/" ||
88 origin.spec() == "https://plus.google.com/" ||
89 origin.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/" ||
90 origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
91 origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
92 origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/") {
93 return true;
94 }
95 // Check against hashed origins.
96 const std::string origin_hash = base::SHA1HashString(origin.spec());
97 DCHECK_EQ(origin_hash.length(), base::kSHA1Length);
98 const std::string hexencoded_origin_hash =
99 base::HexEncode(origin_hash.data(), origin_hash.length());
100 return
101 hexencoded_origin_hash == "3C2705BC432E7C51CA8553FDC5BEE873FF2468EE" ||
102 hexencoded_origin_hash == "50F02B8A668CAB274527D58356F07C2143080FCC";
103 #else
104 return false;
105 #endif
106 }
107
108 // Helper to get title of the calling application shown in the screen capture
109 // notification.
GetApplicationTitle(content::WebContents * web_contents,const extensions::Extension * extension)110 base::string16 GetApplicationTitle(content::WebContents* web_contents,
111 const extensions::Extension* extension) {
112 // Use extension name as title for extensions and origin for drive-by web.
113 std::string title;
114 if (extension) {
115 title = extension->name();
116 } else {
117 title = web_contents->GetURL().GetOrigin().spec();
118 }
119 return UTF8ToUTF16(title);
120 }
121
122 // Helper to get list of media stream devices for desktop capture in |devices|.
123 // Registers to display notification if |display_notification| is true.
124 // Returns an instance of MediaStreamUI to be passed to content layer.
GetDevicesForDesktopCapture(content::MediaStreamDevices & devices,content::DesktopMediaID media_id,bool capture_audio,bool display_notification,base::string16 application_title)125 scoped_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
126 content::MediaStreamDevices& devices,
127 content::DesktopMediaID media_id,
128 bool capture_audio,
129 bool display_notification,
130 base::string16 application_title) {
131 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
132 scoped_ptr<content::MediaStreamUI> ui;
133
134 // Add selected desktop source to the list.
135 devices.push_back(content::MediaStreamDevice(
136 content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen"));
137 if (capture_audio) {
138 // Use the special loopback device ID for system audio capture.
139 devices.push_back(content::MediaStreamDevice(
140 content::MEDIA_LOOPBACK_AUDIO_CAPTURE,
141 media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio"));
142 }
143
144 // If required, register to display the notification for stream capture.
145 if (display_notification) {
146 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
147 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT,
148 application_title));
149 }
150
151 return ui.Pass();
152 }
153
154 } // namespace
155
PendingAccessRequest(const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback)156 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
157 const content::MediaStreamRequest& request,
158 const content::MediaResponseCallback& callback)
159 : request(request),
160 callback(callback) {
161 }
162
~PendingAccessRequest()163 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {}
164
GetInstance()165 MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() {
166 return Singleton<MediaCaptureDevicesDispatcher>::get();
167 }
168
MediaCaptureDevicesDispatcher()169 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
170 : devices_enumerated_(false),
171 is_device_enumeration_disabled_(false),
172 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()),
173 audio_stream_indicator_(new AudioStreamIndicator()) {
174 // MediaCaptureDevicesDispatcher is a singleton. It should be created on
175 // UI thread. Otherwise, it will not receive
176 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
177 // possible use after free.
178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179 notifications_registrar_.Add(
180 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
181 content::NotificationService::AllSources());
182 }
183
~MediaCaptureDevicesDispatcher()184 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
185
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)186 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
187 user_prefs::PrefRegistrySyncable* registry) {
188 registry->RegisterStringPref(
189 prefs::kDefaultAudioCaptureDevice,
190 std::string(),
191 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
192 registry->RegisterStringPref(
193 prefs::kDefaultVideoCaptureDevice,
194 std::string(),
195 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
196 }
197
AddObserver(Observer * observer)198 void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) {
199 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
200 if (!observers_.HasObserver(observer))
201 observers_.AddObserver(observer);
202 }
203
RemoveObserver(Observer * observer)204 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) {
205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
206 observers_.RemoveObserver(observer);
207 }
208
209 const MediaStreamDevices&
GetAudioCaptureDevices()210 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
212 if (!is_device_enumeration_disabled_ && !devices_enumerated_) {
213 content::EnsureMonitorCaptureDevices();
214 devices_enumerated_ = true;
215 }
216 return audio_devices_;
217 }
218
219 const MediaStreamDevices&
GetVideoCaptureDevices()220 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
221 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
222 if (!is_device_enumeration_disabled_ && !devices_enumerated_) {
223 content::EnsureMonitorCaptureDevices();
224 devices_enumerated_ = true;
225 }
226 return video_devices_;
227 }
228
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)229 void MediaCaptureDevicesDispatcher::Observe(
230 int type,
231 const content::NotificationSource& source,
232 const content::NotificationDetails& details) {
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234 if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
235 content::WebContents* web_contents =
236 content::Source<content::WebContents>(source).ptr();
237 pending_requests_.erase(web_contents);
238 }
239 }
240
ProcessMediaAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)241 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
242 content::WebContents* web_contents,
243 const content::MediaStreamRequest& request,
244 const content::MediaResponseCallback& callback,
245 const extensions::Extension* extension) {
246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
247
248 if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE ||
249 request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE) {
250 ProcessDesktopCaptureAccessRequest(
251 web_contents, request, callback, extension);
252 } else if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE ||
253 request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
254 ProcessTabCaptureAccessRequest(
255 web_contents, request, callback, extension);
256 } else if (extension && (extension->is_platform_app() ||
257 IsMediaRequestWhitelistedForExtension(extension))) {
258 // For extensions access is approved based on extension permissions.
259 ProcessMediaAccessRequestFromPlatformAppOrExtension(
260 web_contents, request, callback, extension);
261 } else {
262 ProcessRegularMediaAccessRequest(web_contents, request, callback);
263 }
264 }
265
ProcessDesktopCaptureAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)266 void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(
267 content::WebContents* web_contents,
268 const content::MediaStreamRequest& request,
269 const content::MediaResponseCallback& callback,
270 const extensions::Extension* extension) {
271 content::MediaStreamDevices devices;
272 scoped_ptr<content::MediaStreamUI> ui;
273
274 if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
275 callback.Run(devices, ui.Pass());
276 return;
277 }
278
279 // If the device id wasn't specified then this is a screen capture request
280 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
281 if (request.requested_video_device_id.empty()) {
282 ProcessScreenCaptureAccessRequest(
283 web_contents, request, callback, extension);
284 return;
285 }
286
287 // Resolve DesktopMediaID for the specified device id.
288 content::DesktopMediaID media_id =
289 GetDesktopStreamsRegistry()->RequestMediaForStreamId(
290 request.requested_video_device_id, request.render_process_id,
291 request.render_view_id, request.security_origin);
292
293 // Received invalid device id.
294 if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
295 callback.Run(devices, ui.Pass());
296 return;
297 }
298
299 bool loopback_audio_supported = false;
300 #if defined(USE_CRAS) || defined(OS_WIN)
301 // Currently loopback audio capture is supported only on Windows and ChromeOS.
302 loopback_audio_supported = true;
303 #endif
304
305 // Audio is only supported for screen capture streams.
306 bool capture_audio =
307 (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
308 request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
309 loopback_audio_supported);
310
311 ui = GetDevicesForDesktopCapture(
312 devices, media_id, capture_audio, true,
313 GetApplicationTitle(web_contents, extension));
314
315 callback.Run(devices, ui.Pass());
316 }
317
ProcessScreenCaptureAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)318 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
319 content::WebContents* web_contents,
320 const content::MediaStreamRequest& request,
321 const content::MediaResponseCallback& callback,
322 const extensions::Extension* extension) {
323 content::MediaStreamDevices devices;
324 scoped_ptr<content::MediaStreamUI> ui;
325
326 DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE);
327
328 bool loopback_audio_supported = false;
329 #if defined(USE_CRAS) || defined(OS_WIN)
330 // Currently loopback audio capture is supported only on Windows and ChromeOS.
331 loopback_audio_supported = true;
332 #endif
333
334 const bool component_extension =
335 extension && extension->location() == extensions::Manifest::COMPONENT;
336
337 const bool screen_capture_enabled =
338 CommandLine::ForCurrentProcess()->HasSwitch(
339 switches::kEnableUserMediaScreenCapturing) ||
340 IsOriginWhitelistedForScreenCapture(request.security_origin);
341
342 const bool origin_is_secure =
343 request.security_origin.SchemeIsSecure() ||
344 request.security_origin.SchemeIs(extensions::kExtensionScheme) ||
345 CommandLine::ForCurrentProcess()->HasSwitch(
346 switches::kAllowHttpScreenCapture);
347
348 // Approve request only when the following conditions are met:
349 // 1. Screen capturing is enabled via command line switch or white-listed for
350 // the given origin.
351 // 2. Request comes from a page with a secure origin or from an extension.
352 if (screen_capture_enabled && origin_is_secure) {
353 // Get title of the calling application prior to showing the message box.
354 // chrome::ShowMessageBox() starts a nested message loop which may allow
355 // |web_contents| to be destroyed on the UI thread before the message box
356 // is closed. See http://crbug.com/326690.
357 base::string16 application_title =
358 GetApplicationTitle(web_contents, extension);
359 web_contents = NULL;
360
361 // For component extensions, bypass message box.
362 bool user_approved = false;
363 if (!component_extension) {
364 base::string16 application_name = UTF8ToUTF16(
365 extension ? extension->name() : request.security_origin.spec());
366 base::string16 confirmation_text = l10n_util::GetStringFUTF16(
367 request.audio_type == content::MEDIA_NO_SERVICE ?
368 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT :
369 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
370 application_name);
371 chrome::MessageBoxResult result = chrome::ShowMessageBox(
372 NULL,
373 l10n_util::GetStringFUTF16(
374 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
375 confirmation_text,
376 chrome::MESSAGE_BOX_TYPE_QUESTION);
377 user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
378 }
379
380 if (user_approved || component_extension) {
381 content::DesktopMediaID screen_id;
382 #if defined(OS_CHROMEOS)
383 screen_id = content::DesktopMediaID::RegisterAuraWindow(
384 ash::Shell::GetInstance()->GetPrimaryRootWindow());
385 #else // defined(OS_CHROMEOS)
386 screen_id =
387 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
388 #endif // !defined(OS_CHROMEOS)
389
390 bool capture_audio =
391 (request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
392 loopback_audio_supported);
393
394 // Unless we're being invoked from a component extension, register to
395 // display the notification for stream capture.
396 bool display_notification = !component_extension;
397
398 ui = GetDevicesForDesktopCapture(devices, screen_id, capture_audio,
399 display_notification, application_title);
400 }
401 }
402
403 callback.Run(devices, ui.Pass());
404 }
405
ProcessTabCaptureAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)406 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
407 content::WebContents* web_contents,
408 const content::MediaStreamRequest& request,
409 const content::MediaResponseCallback& callback,
410 const extensions::Extension* extension) {
411 content::MediaStreamDevices devices;
412 scoped_ptr<content::MediaStreamUI> ui;
413
414 #if defined(OS_ANDROID)
415 // Tab capture is not supported on Android.
416 callback.Run(devices, ui.Pass());
417 #else // defined(OS_ANDROID)
418 Profile* profile =
419 Profile::FromBrowserContext(web_contents->GetBrowserContext());
420 extensions::TabCaptureRegistry* tab_capture_registry =
421 extensions::TabCaptureRegistry::Get(profile);
422 if (!tab_capture_registry) {
423 NOTREACHED();
424 callback.Run(devices, ui.Pass());
425 return;
426 }
427 bool tab_capture_allowed =
428 tab_capture_registry->VerifyRequest(request.render_process_id,
429 request.render_view_id);
430
431 if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE &&
432 tab_capture_allowed &&
433 extension->HasAPIPermission(extensions::APIPermission::kTabCapture)) {
434 devices.push_back(content::MediaStreamDevice(
435 content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
436 }
437
438 if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE &&
439 tab_capture_allowed &&
440 extension->HasAPIPermission(extensions::APIPermission::kTabCapture)) {
441 devices.push_back(content::MediaStreamDevice(
442 content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
443 }
444
445 if (!devices.empty()) {
446 ui = media_stream_capture_indicator_->RegisterMediaStream(
447 web_contents, devices);
448 }
449 callback.Run(devices, ui.Pass());
450 #endif // !defined(OS_ANDROID)
451 }
452
453 void MediaCaptureDevicesDispatcher::
ProcessMediaAccessRequestFromPlatformAppOrExtension(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)454 ProcessMediaAccessRequestFromPlatformAppOrExtension(
455 content::WebContents* web_contents,
456 const content::MediaStreamRequest& request,
457 const content::MediaResponseCallback& callback,
458 const extensions::Extension* extension) {
459 content::MediaStreamDevices devices;
460 Profile* profile =
461 Profile::FromBrowserContext(web_contents->GetBrowserContext());
462
463 if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
464 extension->HasAPIPermission(extensions::APIPermission::kAudioCapture)) {
465 GetDefaultDevicesForProfile(profile, true, false, &devices);
466 }
467
468 if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
469 extension->HasAPIPermission(extensions::APIPermission::kVideoCapture)) {
470 GetDefaultDevicesForProfile(profile, false, true, &devices);
471 }
472
473 scoped_ptr<content::MediaStreamUI> ui;
474 if (!devices.empty()) {
475 ui = media_stream_capture_indicator_->RegisterMediaStream(
476 web_contents, devices);
477 }
478 callback.Run(devices, ui.Pass());
479 }
480
ProcessRegularMediaAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback)481 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
482 content::WebContents* web_contents,
483 const content::MediaStreamRequest& request,
484 const content::MediaResponseCallback& callback) {
485 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
486
487 RequestsQueue& queue = pending_requests_[web_contents];
488 queue.push_back(PendingAccessRequest(request, callback));
489
490 // If this is the only request then show the infobar.
491 if (queue.size() == 1)
492 ProcessQueuedAccessRequest(web_contents);
493 }
494
ProcessQueuedAccessRequest(content::WebContents * web_contents)495 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
496 content::WebContents* web_contents) {
497 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
498
499 std::map<content::WebContents*, RequestsQueue>::iterator it =
500 pending_requests_.find(web_contents);
501
502 if (it == pending_requests_.end() || it->second.empty()) {
503 // Don't do anything if the tab was was closed.
504 return;
505 }
506
507 DCHECK(!it->second.empty());
508
509 MediaStreamInfoBarDelegate::Create(
510 web_contents, it->second.front().request,
511 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
512 base::Unretained(this), web_contents));
513 }
514
OnAccessRequestResponse(content::WebContents * web_contents,const content::MediaStreamDevices & devices,scoped_ptr<content::MediaStreamUI> ui)515 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
516 content::WebContents* web_contents,
517 const content::MediaStreamDevices& devices,
518 scoped_ptr<content::MediaStreamUI> ui) {
519 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
520
521 std::map<content::WebContents*, RequestsQueue>::iterator it =
522 pending_requests_.find(web_contents);
523 if (it == pending_requests_.end()) {
524 // WebContents has been destroyed. Don't need to do anything.
525 return;
526 }
527
528 RequestsQueue& queue(it->second);
529 if (queue.empty())
530 return;
531
532 content::MediaResponseCallback callback = queue.front().callback;
533 queue.pop_front();
534
535 if (!queue.empty()) {
536 // Post a task to process next queued request. It has to be done
537 // asynchronously to make sure that calling infobar is not destroyed until
538 // after this function returns.
539 BrowserThread::PostTask(
540 BrowserThread::UI, FROM_HERE,
541 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest,
542 base::Unretained(this), web_contents));
543 }
544
545 callback.Run(devices, ui.Pass());
546 }
547
GetDefaultDevicesForProfile(Profile * profile,bool audio,bool video,content::MediaStreamDevices * devices)548 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
549 Profile* profile,
550 bool audio,
551 bool video,
552 content::MediaStreamDevices* devices) {
553 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
554 DCHECK(audio || video);
555
556 PrefService* prefs = profile->GetPrefs();
557 std::string default_device;
558 if (audio) {
559 default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
560 const content::MediaStreamDevice* device =
561 GetRequestedAudioDevice(default_device);
562 if (!device)
563 device = GetFirstAvailableAudioDevice();
564 if (device)
565 devices->push_back(*device);
566 }
567
568 if (video) {
569 default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
570 const content::MediaStreamDevice* device =
571 GetRequestedVideoDevice(default_device);
572 if (!device)
573 device = GetFirstAvailableVideoDevice();
574 if (device)
575 devices->push_back(*device);
576 }
577 }
578
579 const content::MediaStreamDevice*
GetRequestedAudioDevice(const std::string & requested_audio_device_id)580 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
581 const std::string& requested_audio_device_id) {
582 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
583 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
584 const content::MediaStreamDevice* const device =
585 FindDeviceWithId(audio_devices, requested_audio_device_id);
586 return device;
587 }
588
589 const content::MediaStreamDevice*
GetFirstAvailableAudioDevice()590 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
591 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
592 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
593 if (audio_devices.empty())
594 return NULL;
595 return &(*audio_devices.begin());
596 }
597
598 const content::MediaStreamDevice*
GetRequestedVideoDevice(const std::string & requested_video_device_id)599 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
600 const std::string& requested_video_device_id) {
601 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
602 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
603 const content::MediaStreamDevice* const device =
604 FindDeviceWithId(video_devices, requested_video_device_id);
605 return device;
606 }
607
608 const content::MediaStreamDevice*
GetFirstAvailableVideoDevice()609 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
610 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
611 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
612 if (video_devices.empty())
613 return NULL;
614 return &(*video_devices.begin());
615 }
616
DisableDeviceEnumerationForTesting()617 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
618 is_device_enumeration_disabled_ = true;
619 }
620
621 scoped_refptr<MediaStreamCaptureIndicator>
GetMediaStreamCaptureIndicator()622 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
623 return media_stream_capture_indicator_;
624 }
625
626 scoped_refptr<AudioStreamIndicator>
GetAudioStreamIndicator()627 MediaCaptureDevicesDispatcher::GetAudioStreamIndicator() {
628 return audio_stream_indicator_;
629 }
630
631 DesktopStreamsRegistry*
GetDesktopStreamsRegistry()632 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
633 if (!desktop_streams_registry_)
634 desktop_streams_registry_.reset(new DesktopStreamsRegistry());
635 return desktop_streams_registry_.get();
636 }
637
OnAudioCaptureDevicesChanged(const content::MediaStreamDevices & devices)638 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged(
639 const content::MediaStreamDevices& devices) {
640 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
641 BrowserThread::PostTask(
642 BrowserThread::UI, FROM_HERE,
643 base::Bind(&MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread,
644 base::Unretained(this), devices));
645 }
646
OnVideoCaptureDevicesChanged(const content::MediaStreamDevices & devices)647 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged(
648 const content::MediaStreamDevices& devices) {
649 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
650 BrowserThread::PostTask(
651 BrowserThread::UI, FROM_HERE,
652 base::Bind(&MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread,
653 base::Unretained(this), devices));
654 }
655
OnMediaRequestStateChanged(int render_process_id,int render_view_id,int page_request_id,const content::MediaStreamDevice & device,content::MediaRequestState state)656 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
657 int render_process_id,
658 int render_view_id,
659 int page_request_id,
660 const content::MediaStreamDevice& device,
661 content::MediaRequestState state) {
662 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
663 BrowserThread::PostTask(
664 BrowserThread::UI, FROM_HERE,
665 base::Bind(
666 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
667 base::Unretained(this), render_process_id, render_view_id,
668 page_request_id, device, state));
669 }
670
OnAudioStreamPlayingChanged(int render_process_id,int render_view_id,int stream_id,bool is_playing,float power_dbfs,bool clipped)671 void MediaCaptureDevicesDispatcher::OnAudioStreamPlayingChanged(
672 int render_process_id, int render_view_id, int stream_id,
673 bool is_playing, float power_dbfs, bool clipped) {
674 audio_stream_indicator_->UpdateWebContentsStatus(
675 render_process_id, render_view_id, stream_id,
676 is_playing, power_dbfs, clipped);
677 }
678
OnCreatingAudioStream(int render_process_id,int render_view_id)679 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
680 int render_process_id,
681 int render_view_id) {
682 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
683 BrowserThread::PostTask(
684 BrowserThread::UI, FROM_HERE,
685 base::Bind(
686 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread,
687 base::Unretained(this), render_process_id, render_view_id));
688 }
689
UpdateAudioDevicesOnUIThread(const content::MediaStreamDevices & devices)690 void MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread(
691 const content::MediaStreamDevices& devices) {
692 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
693 devices_enumerated_ = true;
694 audio_devices_ = devices;
695 FOR_EACH_OBSERVER(Observer, observers_,
696 OnUpdateAudioDevices(audio_devices_));
697 }
698
UpdateVideoDevicesOnUIThread(const content::MediaStreamDevices & devices)699 void MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread(
700 const content::MediaStreamDevices& devices) {
701 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
702 devices_enumerated_ = true;
703 video_devices_ = devices;
704 FOR_EACH_OBSERVER(Observer, observers_,
705 OnUpdateVideoDevices(video_devices_));
706 }
707
UpdateMediaRequestStateOnUIThread(int render_process_id,int render_view_id,int page_request_id,const content::MediaStreamDevice & device,content::MediaRequestState state)708 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
709 int render_process_id,
710 int render_view_id,
711 int page_request_id,
712 const content::MediaStreamDevice& device,
713 content::MediaRequestState state) {
714 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
715 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
716 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
717 if (device.type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
718 if (state == content::MEDIA_REQUEST_STATE_DONE) {
719 DesktopCaptureSession session = { render_process_id, render_view_id,
720 page_request_id };
721 desktop_capture_sessions_.push_back(session);
722 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
723 for (DesktopCaptureSessions::iterator it =
724 desktop_capture_sessions_.begin();
725 it != desktop_capture_sessions_.end();
726 ++it) {
727 if (it->render_process_id == render_process_id &&
728 it->render_view_id == render_view_id &&
729 it->page_request_id == page_request_id) {
730 desktop_capture_sessions_.erase(it);
731 break;
732 }
733 }
734 }
735 }
736
737 // Cancel the request.
738 if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
739 bool found = false;
740 for (RequestsQueues::iterator rqs_it = pending_requests_.begin();
741 rqs_it != pending_requests_.end(); ++rqs_it) {
742 RequestsQueue& queue = rqs_it->second;
743 for (RequestsQueue::iterator it = queue.begin();
744 it != queue.end(); ++it) {
745 if (it->request.render_process_id == render_process_id &&
746 it->request.render_view_id == render_view_id &&
747 it->request.page_request_id == page_request_id) {
748 queue.erase(it);
749 found = true;
750 break;
751 }
752 }
753 if (found)
754 break;
755 }
756 }
757
758 FOR_EACH_OBSERVER(Observer, observers_,
759 OnRequestUpdate(render_process_id,
760 render_view_id,
761 device,
762 state));
763 }
764
OnCreatingAudioStreamOnUIThread(int render_process_id,int render_view_id)765 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
766 int render_process_id,
767 int render_view_id) {
768 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
769 FOR_EACH_OBSERVER(Observer, observers_,
770 OnCreatingAudioStream(render_process_id, render_view_id));
771 }
772
IsDesktopCaptureInProgress()773 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
774 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
775 return desktop_capture_sessions_.size() > 0;
776 }
777