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.
4
5 #include "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
6
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_service.h"
12 #include "chrome/browser/extensions/extension_tab_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "content/public/browser/media_device_id.h"
15 #include "content/public/browser/resource_context.h"
16 #include "content/public/browser/web_contents.h"
17 #include "extensions/browser/event_router.h"
18 #include "extensions/browser/extension_system.h"
19 #include "extensions/common/error_utils.h"
20 #include "extensions/common/permissions/permissions_data.h"
21 #include "media/audio/audio_manager_base.h"
22 #include "media/audio/audio_output_controller.h"
23
24 namespace extensions {
25
26 using content::BrowserThread;
27 using content::RenderViewHost;
28 using media::AudioDeviceNames;
29 using media::AudioManager;
30
31 namespace wap = api::webrtc_audio_private;
32
33 static base::LazyInstance<
34 BrowserContextKeyedAPIFactory<WebrtcAudioPrivateEventService> > g_factory =
35 LAZY_INSTANCE_INITIALIZER;
36
WebrtcAudioPrivateEventService(content::BrowserContext * context)37 WebrtcAudioPrivateEventService::WebrtcAudioPrivateEventService(
38 content::BrowserContext* context)
39 : browser_context_(context) {
40 // In unit tests, the SystemMonitor may not be created.
41 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
42 if (system_monitor)
43 system_monitor->AddDevicesChangedObserver(this);
44 }
45
~WebrtcAudioPrivateEventService()46 WebrtcAudioPrivateEventService::~WebrtcAudioPrivateEventService() {
47 }
48
Shutdown()49 void WebrtcAudioPrivateEventService::Shutdown() {
50 // In unit tests, the SystemMonitor may not be created.
51 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
52 if (system_monitor)
53 system_monitor->RemoveDevicesChangedObserver(this);
54 }
55
56 // static
57 BrowserContextKeyedAPIFactory<WebrtcAudioPrivateEventService>*
GetFactoryInstance()58 WebrtcAudioPrivateEventService::GetFactoryInstance() {
59 return g_factory.Pointer();
60 }
61
62 // static
service_name()63 const char* WebrtcAudioPrivateEventService::service_name() {
64 return "WebrtcAudioPrivateEventService";
65 }
66
OnDevicesChanged(base::SystemMonitor::DeviceType device_type)67 void WebrtcAudioPrivateEventService::OnDevicesChanged(
68 base::SystemMonitor::DeviceType device_type) {
69 switch (device_type) {
70 case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE:
71 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
72 SignalEvent();
73 break;
74
75 default:
76 // No action needed.
77 break;
78 }
79 }
80
SignalEvent()81 void WebrtcAudioPrivateEventService::SignalEvent() {
82 using api::webrtc_audio_private::OnSinksChanged::kEventName;
83
84 EventRouter* router = EventRouter::Get(browser_context_);
85 if (!router || !router->HasEventListener(kEventName))
86 return;
87 ExtensionService* extension_service =
88 ExtensionSystem::Get(browser_context_)->extension_service();
89 const ExtensionSet* extensions = extension_service->extensions();
90 for (ExtensionSet::const_iterator it = extensions->begin();
91 it != extensions->end(); ++it) {
92 const std::string& extension_id = (*it)->id();
93 if (router->ExtensionHasEventListener(extension_id, kEventName) &&
94 (*it)->permissions_data()->HasAPIPermission("webrtcAudioPrivate")) {
95 scoped_ptr<Event> event(
96 new Event(kEventName, make_scoped_ptr(new base::ListValue()).Pass()));
97 router->DispatchEventToExtension(extension_id, event.Pass());
98 }
99 }
100 }
101
WebrtcAudioPrivateFunction()102 WebrtcAudioPrivateFunction::WebrtcAudioPrivateFunction()
103 : resource_context_(NULL) {
104 }
105
~WebrtcAudioPrivateFunction()106 WebrtcAudioPrivateFunction::~WebrtcAudioPrivateFunction() {
107 }
108
GetOutputDeviceNames()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(
115 FROM_HERE,
116 base::Bind(&WebrtcAudioPrivateFunction::GetOutputDeviceNames, this));
117 return;
118 }
119
120 scoped_ptr<AudioDeviceNames> device_names(new AudioDeviceNames);
121 AudioManager::Get()->GetAudioOutputDeviceNames(device_names.get());
122
123 BrowserThread::PostTask(
124 BrowserThread::IO,
125 FROM_HERE,
126 base::Bind(&WebrtcAudioPrivateFunction::OnOutputDeviceNames,
127 this,
128 Passed(&device_names)));
129 }
130
OnOutputDeviceNames(scoped_ptr<AudioDeviceNames> device_names)131 void WebrtcAudioPrivateFunction::OnOutputDeviceNames(
132 scoped_ptr<AudioDeviceNames> device_names) {
133 NOTREACHED();
134 }
135
GetControllerList(int tab_id)136 bool WebrtcAudioPrivateFunction::GetControllerList(int tab_id) {
137 content::WebContents* contents = NULL;
138 if (!ExtensionTabUtil::GetTabById(
139 tab_id, GetProfile(), true, NULL, NULL, &contents, NULL)) {
140 error_ = extensions::ErrorUtils::FormatErrorMessage(
141 extensions::tabs_constants::kTabNotFoundError,
142 base::IntToString(tab_id));
143 return false;
144 }
145
146 RenderViewHost* rvh = contents->GetRenderViewHost();
147 if (!rvh)
148 return false;
149
150 rvh->GetAudioOutputControllers(base::Bind(
151 &WebrtcAudioPrivateFunction::OnControllerList, this));
152 return true;
153 }
154
OnControllerList(const content::RenderViewHost::AudioOutputControllerList & list)155 void WebrtcAudioPrivateFunction::OnControllerList(
156 const content::RenderViewHost::AudioOutputControllerList& list) {
157 NOTREACHED();
158 }
159
CalculateHMAC(const std::string & raw_id)160 void WebrtcAudioPrivateFunction::CalculateHMAC(const std::string& raw_id) {
161 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
162 BrowserThread::PostTask(
163 BrowserThread::IO,
164 FROM_HERE,
165 base::Bind(&WebrtcAudioPrivateFunction::CalculateHMAC, this, raw_id));
166 return;
167 }
168
169 std::string hmac = CalculateHMACImpl(raw_id);
170 BrowserThread::PostTask(
171 BrowserThread::UI,
172 FROM_HERE,
173 base::Bind(&WebrtcAudioPrivateFunction::OnHMACCalculated, this, hmac));
174 }
175
OnHMACCalculated(const std::string & hmac)176 void WebrtcAudioPrivateFunction::OnHMACCalculated(const std::string& hmac) {
177 NOTREACHED();
178 }
179
CalculateHMACImpl(const std::string & raw_id)180 std::string WebrtcAudioPrivateFunction::CalculateHMACImpl(
181 const std::string& raw_id) {
182 DCHECK_CURRENTLY_ON(BrowserThread::IO);
183
184 // We don't hash the default device name, and we always return
185 // "default" for the default device. There is code in SetActiveSink
186 // that transforms "default" to the empty string, and code in
187 // GetActiveSink that ensures we return "default" if we get the
188 // empty string as the current device ID.
189 if (raw_id.empty() || raw_id == media::AudioManagerBase::kDefaultDeviceId)
190 return media::AudioManagerBase::kDefaultDeviceId;
191
192 GURL security_origin(source_url().GetOrigin());
193 return content::GetHMACForMediaDeviceID(
194 resource_context()->GetMediaDeviceIDSalt(),
195 security_origin,
196 raw_id);
197 }
198
InitResourceContext()199 void WebrtcAudioPrivateFunction::InitResourceContext() {
200 resource_context_ = GetProfile()->GetResourceContext();
201 }
202
resource_context() const203 content::ResourceContext* WebrtcAudioPrivateFunction::resource_context() const {
204 DCHECK(resource_context_); // Did you forget to InitResourceContext()?
205 return resource_context_;
206 }
207
RunAsync()208 bool WebrtcAudioPrivateGetSinksFunction::RunAsync() {
209 DCHECK_CURRENTLY_ON(BrowserThread::UI);
210
211 InitResourceContext();
212 GetOutputDeviceNames();
213
214 return true;
215 }
216
OnOutputDeviceNames(scoped_ptr<AudioDeviceNames> raw_ids)217 void WebrtcAudioPrivateGetSinksFunction::OnOutputDeviceNames(
218 scoped_ptr<AudioDeviceNames> raw_ids) {
219 DCHECK_CURRENTLY_ON(BrowserThread::IO);
220
221 std::vector<linked_ptr<wap::SinkInfo> > results;
222 for (AudioDeviceNames::const_iterator it = raw_ids->begin();
223 it != raw_ids->end();
224 ++it) {
225 linked_ptr<wap::SinkInfo> info(new wap::SinkInfo);
226 info->sink_id = CalculateHMACImpl(it->unique_id);
227 info->sink_label = it->device_name;
228 // TODO(joi): Add other parameters.
229 results.push_back(info);
230 }
231
232 // It's safe to directly set the results here (from a thread other
233 // than the UI thread, on which an AsyncExtensionFunction otherwise
234 // normally runs) because there is one instance of this object per
235 // function call, no actor outside of this object is modifying the
236 // results_ member, and the different method invocations on this
237 // object run strictly in sequence; first RunAsync on the UI thread,
238 // then DoQuery on the audio IO thread, then DoneOnUIThread on the
239 // UI thread.
240 results_.reset(wap::GetSinks::Results::Create(results).release());
241
242 BrowserThread::PostTask(
243 BrowserThread::UI,
244 FROM_HERE,
245 base::Bind(&WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread, this));
246 }
247
DoneOnUIThread()248 void WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread() {
249 SendResponse(true);
250 }
251
RunAsync()252 bool WebrtcAudioPrivateGetActiveSinkFunction::RunAsync() {
253 DCHECK_CURRENTLY_ON(BrowserThread::UI);
254 InitResourceContext();
255
256 scoped_ptr<wap::GetActiveSink::Params> params(
257 wap::GetActiveSink::Params::Create(*args_));
258 EXTENSION_FUNCTION_VALIDATE(params.get());
259
260 return GetControllerList(params->tab_id);
261 }
262
OnControllerList(const RenderViewHost::AudioOutputControllerList & controllers)263 void WebrtcAudioPrivateGetActiveSinkFunction::OnControllerList(
264 const RenderViewHost::AudioOutputControllerList& controllers) {
265 DCHECK_CURRENTLY_ON(BrowserThread::UI);
266
267 if (controllers.empty()) {
268 // If there is no current audio stream for the rvh, we return an
269 // empty string as the sink ID.
270 DVLOG(2) << "chrome.webrtcAudioPrivate.getActiveSink: No controllers.";
271 results_.reset(
272 wap::GetActiveSink::Results::Create(std::string()).release());
273 SendResponse(true);
274 } else {
275 DVLOG(2) << "chrome.webrtcAudioPrivate.getActiveSink: "
276 << controllers.size() << " controllers.";
277 // TODO(joi): Debug-only, DCHECK that all items have the same ID.
278
279 // Send the raw ID through CalculateHMAC, and send the result in
280 // OnHMACCalculated.
281 (*controllers.begin())->GetOutputDeviceId(
282 base::Bind(&WebrtcAudioPrivateGetActiveSinkFunction::CalculateHMAC,
283 this));
284 }
285 }
286
OnHMACCalculated(const std::string & hmac_id)287 void WebrtcAudioPrivateGetActiveSinkFunction::OnHMACCalculated(
288 const std::string& hmac_id) {
289 DCHECK_CURRENTLY_ON(BrowserThread::UI);
290
291 std::string result = hmac_id;
292 if (result.empty()) {
293 DVLOG(2) << "Received empty ID, replacing with default ID.";
294 result = media::AudioManagerBase::kDefaultDeviceId;
295 }
296 results_.reset(wap::GetActiveSink::Results::Create(result).release());
297 SendResponse(true);
298 }
299
300 WebrtcAudioPrivateSetActiveSinkFunction::
WebrtcAudioPrivateSetActiveSinkFunction()301 WebrtcAudioPrivateSetActiveSinkFunction()
302 : tab_id_(0),
303 num_remaining_sink_ids_(0) {
304 }
305
306 WebrtcAudioPrivateSetActiveSinkFunction::
~WebrtcAudioPrivateSetActiveSinkFunction()307 ~WebrtcAudioPrivateSetActiveSinkFunction() {
308 }
309
RunAsync()310 bool WebrtcAudioPrivateSetActiveSinkFunction::RunAsync() {
311 DCHECK_CURRENTLY_ON(BrowserThread::UI);
312 scoped_ptr<wap::SetActiveSink::Params> params(
313 wap::SetActiveSink::Params::Create(*args_));
314 EXTENSION_FUNCTION_VALIDATE(params.get());
315
316 InitResourceContext();
317
318 tab_id_ = params->tab_id;
319 sink_id_ = params->sink_id;
320
321 return GetControllerList(tab_id_);
322 }
323
OnControllerList(const RenderViewHost::AudioOutputControllerList & controllers)324 void WebrtcAudioPrivateSetActiveSinkFunction::OnControllerList(
325 const RenderViewHost::AudioOutputControllerList& controllers) {
326 DCHECK_CURRENTLY_ON(BrowserThread::UI);
327
328 controllers_ = controllers;
329 num_remaining_sink_ids_ = controllers_.size();
330 if (num_remaining_sink_ids_ == 0) {
331 error_ = extensions::ErrorUtils::FormatErrorMessage(
332 "No active stream for tab with id: *.",
333 base::IntToString(tab_id_));
334 SendResponse(false);
335 } else {
336 // We need to get the output device names, and calculate the HMAC
337 // for each, to find the raw ID for the ID provided to this API
338 // function call.
339 GetOutputDeviceNames();
340 }
341 }
342
OnOutputDeviceNames(scoped_ptr<AudioDeviceNames> device_names)343 void WebrtcAudioPrivateSetActiveSinkFunction::OnOutputDeviceNames(
344 scoped_ptr<AudioDeviceNames> device_names) {
345 DCHECK_CURRENTLY_ON(BrowserThread::IO);
346
347 std::string raw_sink_id;
348 if (sink_id_ == media::AudioManagerBase::kDefaultDeviceId) {
349 DVLOG(2) << "Received default ID, replacing with empty ID.";
350 raw_sink_id = "";
351 } else {
352 for (AudioDeviceNames::const_iterator it = device_names->begin();
353 it != device_names->end();
354 ++it) {
355 if (sink_id_ == CalculateHMACImpl(it->unique_id)) {
356 raw_sink_id = it->unique_id;
357 break;
358 }
359 }
360
361 if (raw_sink_id.empty())
362 DVLOG(2) << "Found no matching raw sink ID for HMAC " << sink_id_;
363 }
364
365 RenderViewHost::AudioOutputControllerList::const_iterator it =
366 controllers_.begin();
367 for (; it != controllers_.end(); ++it) {
368 (*it)->SwitchOutputDevice(raw_sink_id, base::Bind(
369 &WebrtcAudioPrivateSetActiveSinkFunction::SwitchDone, this));
370 }
371 }
372
SwitchDone()373 void WebrtcAudioPrivateSetActiveSinkFunction::SwitchDone() {
374 if (--num_remaining_sink_ids_ == 0) {
375 BrowserThread::PostTask(
376 BrowserThread::UI,
377 FROM_HERE,
378 base::Bind(&WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread,
379 this));
380 }
381 }
382
DoneOnUIThread()383 void WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread() {
384 SendResponse(true);
385 }
386
387 WebrtcAudioPrivateGetAssociatedSinkFunction::
WebrtcAudioPrivateGetAssociatedSinkFunction()388 WebrtcAudioPrivateGetAssociatedSinkFunction() {
389 }
390
391 WebrtcAudioPrivateGetAssociatedSinkFunction::
~WebrtcAudioPrivateGetAssociatedSinkFunction()392 ~WebrtcAudioPrivateGetAssociatedSinkFunction() {
393 }
394
RunAsync()395 bool WebrtcAudioPrivateGetAssociatedSinkFunction::RunAsync() {
396 params_ = wap::GetAssociatedSink::Params::Create(*args_);
397 DCHECK_CURRENTLY_ON(BrowserThread::UI);
398 EXTENSION_FUNCTION_VALIDATE(params_.get());
399
400 InitResourceContext();
401
402 AudioManager::Get()->GetWorkerTaskRunner()->PostTask(
403 FROM_HERE,
404 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
405 GetDevicesOnDeviceThread, this));
406
407 return true;
408 }
409
GetDevicesOnDeviceThread()410 void WebrtcAudioPrivateGetAssociatedSinkFunction::GetDevicesOnDeviceThread() {
411 DCHECK(AudioManager::Get()->GetWorkerTaskRunner()->BelongsToCurrentThread());
412 AudioManager::Get()->GetAudioInputDeviceNames(&source_devices_);
413
414 BrowserThread::PostTask(
415 BrowserThread::IO,
416 FROM_HERE,
417 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
418 GetRawSourceIDOnIOThread,
419 this));
420 }
421
422 void
GetRawSourceIDOnIOThread()423 WebrtcAudioPrivateGetAssociatedSinkFunction::GetRawSourceIDOnIOThread() {
424 DCHECK_CURRENTLY_ON(BrowserThread::IO);
425
426 GURL security_origin(params_->security_origin);
427 std::string source_id_in_origin(params_->source_id_in_origin);
428
429 // Find the raw source ID for source_id_in_origin.
430 std::string raw_source_id;
431 for (AudioDeviceNames::const_iterator it = source_devices_.begin();
432 it != source_devices_.end();
433 ++it) {
434 const std::string& id = it->unique_id;
435 if (content::DoesMediaDeviceIDMatchHMAC(
436 resource_context()->GetMediaDeviceIDSalt(),
437 security_origin,
438 source_id_in_origin,
439 id)) {
440 raw_source_id = id;
441 DVLOG(2) << "Found raw ID " << raw_source_id
442 << " for source ID in origin " << source_id_in_origin;
443 break;
444 }
445 }
446
447 AudioManager::Get()->GetWorkerTaskRunner()->PostTask(
448 FROM_HERE,
449 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
450 GetAssociatedSinkOnDeviceThread,
451 this,
452 raw_source_id));
453 }
454
455 void
GetAssociatedSinkOnDeviceThread(const std::string & raw_source_id)456 WebrtcAudioPrivateGetAssociatedSinkFunction::GetAssociatedSinkOnDeviceThread(
457 const std::string& raw_source_id) {
458 DCHECK(AudioManager::Get()->GetWorkerTaskRunner()->BelongsToCurrentThread());
459
460 // We return an empty string if there is no associated output device.
461 std::string raw_sink_id;
462 if (!raw_source_id.empty()) {
463 raw_sink_id =
464 AudioManager::Get()->GetAssociatedOutputDeviceID(raw_source_id);
465 }
466
467 CalculateHMAC(raw_sink_id);
468 }
469
OnHMACCalculated(const std::string & associated_sink_id)470 void WebrtcAudioPrivateGetAssociatedSinkFunction::OnHMACCalculated(
471 const std::string& associated_sink_id) {
472 DCHECK_CURRENTLY_ON(BrowserThread::UI);
473
474 if (associated_sink_id == media::AudioManagerBase::kDefaultDeviceId) {
475 DVLOG(2) << "Got default ID, replacing with empty ID.";
476 results_.reset(wap::GetAssociatedSink::Results::Create("").release());
477 } else {
478 results_.reset(
479 wap::GetAssociatedSink::Results::Create(associated_sink_id).release());
480 }
481
482 SendResponse(true);
483 }
484
485 } // namespace extensions
486