1 // Copyright 2013 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/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
7 #include "base/lazy_instance.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/task_runner_util.h"
10 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
11 #include "chrome/browser/extensions/extension_tab_util.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "content/public/browser/media_device_id.h"
14 #include "content/public/browser/resource_context.h"
15 #include "content/public/browser/web_contents.h"
16 #include "extensions/browser/event_router.h"
17 #include "extensions/browser/extension_registry.h"
18 #include "extensions/common/error_utils.h"
19 #include "extensions/common/permissions/permissions_data.h"
20 #include "media/audio/audio_manager_base.h"
21 #include "media/audio/audio_output_controller.h"
23 namespace extensions
{
25 using content::BrowserThread
;
26 using content::RenderProcessHost
;
27 using media::AudioDeviceNames
;
28 using media::AudioManager
;
30 namespace wap
= api::webrtc_audio_private
;
32 using api::webrtc_audio_private::RequestInfo
;
34 static base::LazyInstance
<
35 BrowserContextKeyedAPIFactory
<WebrtcAudioPrivateEventService
> > g_factory
=
36 LAZY_INSTANCE_INITIALIZER
;
38 WebrtcAudioPrivateEventService::WebrtcAudioPrivateEventService(
39 content::BrowserContext
* context
)
40 : browser_context_(context
) {
41 // In unit tests, the SystemMonitor may not be created.
42 base::SystemMonitor
* system_monitor
= base::SystemMonitor::Get();
44 system_monitor
->AddDevicesChangedObserver(this);
47 WebrtcAudioPrivateEventService::~WebrtcAudioPrivateEventService() {
50 void WebrtcAudioPrivateEventService::Shutdown() {
51 // In unit tests, the SystemMonitor may not be created.
52 base::SystemMonitor
* system_monitor
= base::SystemMonitor::Get();
54 system_monitor
->RemoveDevicesChangedObserver(this);
58 BrowserContextKeyedAPIFactory
<WebrtcAudioPrivateEventService
>*
59 WebrtcAudioPrivateEventService::GetFactoryInstance() {
60 return g_factory
.Pointer();
64 const char* WebrtcAudioPrivateEventService::service_name() {
65 return "WebrtcAudioPrivateEventService";
68 void WebrtcAudioPrivateEventService::OnDevicesChanged(
69 base::SystemMonitor::DeviceType device_type
) {
70 switch (device_type
) {
71 case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE
:
72 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE
:
82 void WebrtcAudioPrivateEventService::SignalEvent() {
83 using api::webrtc_audio_private::OnSinksChanged::kEventName
;
85 EventRouter
* router
= EventRouter::Get(browser_context_
);
86 if (!router
|| !router
->HasEventListener(kEventName
))
89 for (const scoped_refptr
<const extensions::Extension
>& extension
:
90 ExtensionRegistry::Get(browser_context_
)->enabled_extensions()) {
91 const std::string
& extension_id
= extension
->id();
92 if (router
->ExtensionHasEventListener(extension_id
, kEventName
) &&
93 extension
->permissions_data()->HasAPIPermission("webrtcAudioPrivate")) {
94 scoped_ptr
<Event
> event(
95 new Event(events::WEBRTC_AUDIO_PRIVATE_ON_SINKS_CHANGED
, kEventName
,
96 make_scoped_ptr(new base::ListValue()).Pass()));
97 router
->DispatchEventToExtension(extension_id
, event
.Pass());
102 WebrtcAudioPrivateFunction::WebrtcAudioPrivateFunction()
103 : resource_context_(NULL
) {
106 WebrtcAudioPrivateFunction::~WebrtcAudioPrivateFunction() {
109 void WebrtcAudioPrivateFunction::GetOutputDeviceNames() {
110 scoped_refptr
<base::SingleThreadTaskRunner
> audio_manager_runner
=
111 AudioManager::Get()->GetWorkerTaskRunner();
112 if (!audio_manager_runner
->BelongsToCurrentThread()) {
113 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
114 audio_manager_runner
->PostTask(
116 base::Bind(&WebrtcAudioPrivateFunction::GetOutputDeviceNames
, this));
120 scoped_ptr
<AudioDeviceNames
> device_names(new AudioDeviceNames
);
121 AudioManager::Get()->GetAudioOutputDeviceNames(device_names
.get());
123 BrowserThread::PostTask(
126 base::Bind(&WebrtcAudioPrivateFunction::OnOutputDeviceNames
,
128 Passed(&device_names
)));
131 void WebrtcAudioPrivateFunction::OnOutputDeviceNames(
132 scoped_ptr
<AudioDeviceNames
> device_names
) {
136 bool WebrtcAudioPrivateFunction::GetControllerList(const RequestInfo
& request
) {
137 content::RenderProcessHost
* rph
= nullptr;
139 // If |guest_process_id| is defined, directly use this id to find the
140 // corresponding RenderProcessHost.
141 if (request
.guest_process_id
.get()) {
142 rph
= content::RenderProcessHost::FromID(*request
.guest_process_id
.get());
143 } else if (request
.tab_id
.get()) {
144 int tab_id
= *request
.tab_id
.get();
145 content::WebContents
* contents
= NULL
;
146 if (!ExtensionTabUtil::GetTabById(tab_id
, GetProfile(), true, NULL
, NULL
,
148 error_
= extensions::ErrorUtils::FormatErrorMessage(
149 extensions::tabs_constants::kTabNotFoundError
,
150 base::IntToString(tab_id
));
153 rph
= contents
->GetRenderProcessHost();
161 rph
->GetAudioOutputControllers(
162 base::Bind(&WebrtcAudioPrivateFunction::OnControllerList
, this));
166 void WebrtcAudioPrivateFunction::OnControllerList(
167 const content::RenderProcessHost::AudioOutputControllerList
& list
) {
171 void WebrtcAudioPrivateFunction::CalculateHMAC(const std::string
& raw_id
) {
172 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
173 BrowserThread::PostTask(
176 base::Bind(&WebrtcAudioPrivateFunction::CalculateHMAC
, this, raw_id
));
180 std::string hmac
= CalculateHMACImpl(raw_id
);
181 BrowserThread::PostTask(
184 base::Bind(&WebrtcAudioPrivateFunction::OnHMACCalculated
, this, hmac
));
187 void WebrtcAudioPrivateFunction::OnHMACCalculated(const std::string
& hmac
) {
191 std::string
WebrtcAudioPrivateFunction::CalculateHMACImpl(
192 const std::string
& raw_id
) {
193 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
195 // We don't hash the default device name, and we always return
196 // "default" for the default device. There is code in SetActiveSink
197 // that transforms "default" to the empty string, and code in
198 // GetActiveSink that ensures we return "default" if we get the
199 // empty string as the current device ID.
200 if (raw_id
.empty() || raw_id
== media::AudioManagerBase::kDefaultDeviceId
)
201 return media::AudioManagerBase::kDefaultDeviceId
;
203 GURL
security_origin(source_url().GetOrigin());
204 return content::GetHMACForMediaDeviceID(
205 resource_context()->GetMediaDeviceIDSalt(),
210 void WebrtcAudioPrivateFunction::InitResourceContext() {
211 resource_context_
= GetProfile()->GetResourceContext();
214 content::ResourceContext
* WebrtcAudioPrivateFunction::resource_context() const {
215 DCHECK(resource_context_
); // Did you forget to InitResourceContext()?
216 return resource_context_
;
219 bool WebrtcAudioPrivateGetSinksFunction::RunAsync() {
220 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
222 InitResourceContext();
223 GetOutputDeviceNames();
228 void WebrtcAudioPrivateGetSinksFunction::OnOutputDeviceNames(
229 scoped_ptr
<AudioDeviceNames
> raw_ids
) {
230 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
232 std::vector
<linked_ptr
<wap::SinkInfo
> > results
;
233 for (AudioDeviceNames::const_iterator it
= raw_ids
->begin();
234 it
!= raw_ids
->end();
236 linked_ptr
<wap::SinkInfo
> info(new wap::SinkInfo
);
237 info
->sink_id
= CalculateHMACImpl(it
->unique_id
);
238 info
->sink_label
= it
->device_name
;
239 // TODO(joi): Add other parameters.
240 results
.push_back(info
);
243 // It's safe to directly set the results here (from a thread other
244 // than the UI thread, on which an AsyncExtensionFunction otherwise
245 // normally runs) because there is one instance of this object per
246 // function call, no actor outside of this object is modifying the
247 // results_ member, and the different method invocations on this
248 // object run strictly in sequence; first RunAsync on the UI thread,
249 // then DoQuery on the audio IO thread, then DoneOnUIThread on the
251 results_
.reset(wap::GetSinks::Results::Create(results
).release());
253 BrowserThread::PostTask(
256 base::Bind(&WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread
, this));
259 void WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread() {
263 bool WebrtcAudioPrivateGetActiveSinkFunction::RunAsync() {
264 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
265 InitResourceContext();
267 scoped_ptr
<wap::GetActiveSink::Params
> params(
268 wap::GetActiveSink::Params::Create(*args_
));
269 EXTENSION_FUNCTION_VALIDATE(params
.get());
271 return GetControllerList(params
->request
);
274 void WebrtcAudioPrivateGetActiveSinkFunction::OnControllerList(
275 const RenderProcessHost::AudioOutputControllerList
& controllers
) {
276 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
278 if (controllers
.empty()) {
279 // If there is no current audio stream for the rvh, we return an
280 // empty string as the sink ID.
281 DVLOG(2) << "chrome.webrtcAudioPrivate.getActiveSink: No controllers.";
283 wap::GetActiveSink::Results::Create(std::string()).release());
286 DVLOG(2) << "chrome.webrtcAudioPrivate.getActiveSink: "
287 << controllers
.size() << " controllers.";
288 // TODO(joi): Debug-only, DCHECK that all items have the same ID.
290 // Send the raw ID through CalculateHMAC, and send the result in
292 (*controllers
.begin())->GetOutputDeviceId(
293 base::Bind(&WebrtcAudioPrivateGetActiveSinkFunction::CalculateHMAC
,
298 void WebrtcAudioPrivateGetActiveSinkFunction::OnHMACCalculated(
299 const std::string
& hmac_id
) {
300 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
302 std::string result
= hmac_id
;
303 if (result
.empty()) {
304 DVLOG(2) << "Received empty ID, replacing with default ID.";
305 result
= media::AudioManagerBase::kDefaultDeviceId
;
307 results_
.reset(wap::GetActiveSink::Results::Create(result
).release());
311 WebrtcAudioPrivateSetActiveSinkFunction::
312 WebrtcAudioPrivateSetActiveSinkFunction()
313 : num_remaining_sink_ids_(0) {
316 WebrtcAudioPrivateSetActiveSinkFunction::
317 ~WebrtcAudioPrivateSetActiveSinkFunction() {
320 bool WebrtcAudioPrivateSetActiveSinkFunction::RunAsync() {
321 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
322 scoped_ptr
<wap::SetActiveSink::Params
> params(
323 wap::SetActiveSink::Params::Create(*args_
));
324 EXTENSION_FUNCTION_VALIDATE(params
.get());
326 InitResourceContext();
328 if (params
->request
.guest_process_id
.get()) {
329 request_info_
.guest_process_id
.reset(
330 new int(*params
->request
.guest_process_id
.get()));
331 } else if (params
->request
.tab_id
.get()) {
332 request_info_
.tab_id
.reset(new int(*params
->request
.tab_id
.get()));
337 sink_id_
= params
->sink_id
;
339 return GetControllerList(request_info_
);
342 void WebrtcAudioPrivateSetActiveSinkFunction::OnControllerList(
343 const RenderProcessHost::AudioOutputControllerList
& controllers
) {
344 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
346 std::string requested_process_type
;
347 int requested_process_id
;
348 if (request_info_
.guest_process_id
.get()) {
349 requested_process_type
= "guestProcessId";
350 requested_process_id
= *request_info_
.guest_process_id
.get();
352 requested_process_type
= "tabId";
353 requested_process_id
= *request_info_
.tab_id
.get();
356 controllers_
= controllers
;
357 num_remaining_sink_ids_
= controllers_
.size();
358 if (num_remaining_sink_ids_
== 0) {
359 error_
= extensions::ErrorUtils::FormatErrorMessage(
360 "No active stream for " + requested_process_type
+ " *",
361 base::IntToString(requested_process_id
));
364 // We need to get the output device names, and calculate the HMAC
365 // for each, to find the raw ID for the ID provided to this API
367 GetOutputDeviceNames();
371 void WebrtcAudioPrivateSetActiveSinkFunction::OnOutputDeviceNames(
372 scoped_ptr
<AudioDeviceNames
> device_names
) {
373 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
375 std::string raw_sink_id
;
376 if (sink_id_
== media::AudioManagerBase::kDefaultDeviceId
) {
377 DVLOG(2) << "Received default ID, replacing with empty ID.";
380 for (AudioDeviceNames::const_iterator it
= device_names
->begin();
381 it
!= device_names
->end();
383 if (sink_id_
== CalculateHMACImpl(it
->unique_id
)) {
384 raw_sink_id
= it
->unique_id
;
389 if (raw_sink_id
.empty())
390 DVLOG(2) << "Found no matching raw sink ID for HMAC " << sink_id_
;
393 RenderProcessHost::AudioOutputControllerList::const_iterator it
=
394 controllers_
.begin();
395 for (; it
!= controllers_
.end(); ++it
) {
396 (*it
)->SwitchOutputDevice(raw_sink_id
, base::Bind(
397 &WebrtcAudioPrivateSetActiveSinkFunction::SwitchDone
, this));
401 void WebrtcAudioPrivateSetActiveSinkFunction::SwitchDone() {
402 if (--num_remaining_sink_ids_
== 0) {
403 BrowserThread::PostTask(
406 base::Bind(&WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread
,
411 void WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread() {
415 WebrtcAudioPrivateGetAssociatedSinkFunction::
416 WebrtcAudioPrivateGetAssociatedSinkFunction() {
419 WebrtcAudioPrivateGetAssociatedSinkFunction::
420 ~WebrtcAudioPrivateGetAssociatedSinkFunction() {
423 bool WebrtcAudioPrivateGetAssociatedSinkFunction::RunAsync() {
424 params_
= wap::GetAssociatedSink::Params::Create(*args_
);
425 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
426 EXTENSION_FUNCTION_VALIDATE(params_
.get());
428 InitResourceContext();
430 AudioManager::Get()->GetWorkerTaskRunner()->PostTask(
432 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
433 GetDevicesOnDeviceThread
, this));
438 void WebrtcAudioPrivateGetAssociatedSinkFunction::GetDevicesOnDeviceThread() {
439 DCHECK(AudioManager::Get()->GetWorkerTaskRunner()->BelongsToCurrentThread());
440 AudioManager::Get()->GetAudioInputDeviceNames(&source_devices_
);
442 BrowserThread::PostTask(
445 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
446 GetRawSourceIDOnIOThread
,
451 WebrtcAudioPrivateGetAssociatedSinkFunction::GetRawSourceIDOnIOThread() {
452 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
454 GURL
security_origin(params_
->security_origin
);
455 std::string
source_id_in_origin(params_
->source_id_in_origin
);
457 // Find the raw source ID for source_id_in_origin.
458 std::string raw_source_id
;
459 for (AudioDeviceNames::const_iterator it
= source_devices_
.begin();
460 it
!= source_devices_
.end();
462 const std::string
& id
= it
->unique_id
;
463 if (content::DoesMediaDeviceIDMatchHMAC(
464 resource_context()->GetMediaDeviceIDSalt(),
469 DVLOG(2) << "Found raw ID " << raw_source_id
470 << " for source ID in origin " << source_id_in_origin
;
475 AudioManager::Get()->GetWorkerTaskRunner()->PostTask(
477 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
478 GetAssociatedSinkOnDeviceThread
,
484 WebrtcAudioPrivateGetAssociatedSinkFunction::GetAssociatedSinkOnDeviceThread(
485 const std::string
& raw_source_id
) {
486 DCHECK(AudioManager::Get()->GetWorkerTaskRunner()->BelongsToCurrentThread());
488 // We return an empty string if there is no associated output device.
489 std::string raw_sink_id
;
490 if (!raw_source_id
.empty()) {
492 AudioManager::Get()->GetAssociatedOutputDeviceID(raw_source_id
);
495 CalculateHMAC(raw_sink_id
);
498 void WebrtcAudioPrivateGetAssociatedSinkFunction::OnHMACCalculated(
499 const std::string
& associated_sink_id
) {
500 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
502 if (associated_sink_id
== media::AudioManagerBase::kDefaultDeviceId
) {
503 DVLOG(2) << "Got default ID, replacing with empty ID.";
504 results_
.reset(wap::GetAssociatedSink::Results::Create("").release());
507 wap::GetAssociatedSink::Results::Create(associated_sink_id
).release());
513 } // namespace extensions