• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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