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
24namespace extensions {
25
26using content::BrowserThread;
27using content::RenderViewHost;
28using media::AudioDeviceNames;
29using media::AudioManager;
30
31namespace wap = api::webrtc_audio_private;
32
33static base::LazyInstance<
34    BrowserContextKeyedAPIFactory<WebrtcAudioPrivateEventService> > g_factory =
35    LAZY_INSTANCE_INITIALIZER;
36
37WebrtcAudioPrivateEventService::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
46WebrtcAudioPrivateEventService::~WebrtcAudioPrivateEventService() {
47}
48
49void 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
57BrowserContextKeyedAPIFactory<WebrtcAudioPrivateEventService>*
58WebrtcAudioPrivateEventService::GetFactoryInstance() {
59  return g_factory.Pointer();
60}
61
62// static
63const char* WebrtcAudioPrivateEventService::service_name() {
64  return "WebrtcAudioPrivateEventService";
65}
66
67void 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
81void 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
102WebrtcAudioPrivateFunction::WebrtcAudioPrivateFunction()
103    : resource_context_(NULL) {
104}
105
106WebrtcAudioPrivateFunction::~WebrtcAudioPrivateFunction() {
107}
108
109void 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
131void WebrtcAudioPrivateFunction::OnOutputDeviceNames(
132    scoped_ptr<AudioDeviceNames> device_names) {
133  NOTREACHED();
134}
135
136bool 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
155void WebrtcAudioPrivateFunction::OnControllerList(
156    const content::RenderViewHost::AudioOutputControllerList& list) {
157  NOTREACHED();
158}
159
160void 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
176void WebrtcAudioPrivateFunction::OnHMACCalculated(const std::string& hmac) {
177  NOTREACHED();
178}
179
180std::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
199void WebrtcAudioPrivateFunction::InitResourceContext() {
200  resource_context_ = GetProfile()->GetResourceContext();
201}
202
203content::ResourceContext* WebrtcAudioPrivateFunction::resource_context() const {
204  DCHECK(resource_context_);  // Did you forget to InitResourceContext()?
205  return resource_context_;
206}
207
208bool WebrtcAudioPrivateGetSinksFunction::RunAsync() {
209  DCHECK_CURRENTLY_ON(BrowserThread::UI);
210
211  InitResourceContext();
212  GetOutputDeviceNames();
213
214  return true;
215}
216
217void 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
248void WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread() {
249  SendResponse(true);
250}
251
252bool 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
263void 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
287void 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
300WebrtcAudioPrivateSetActiveSinkFunction::
301WebrtcAudioPrivateSetActiveSinkFunction()
302    : tab_id_(0),
303      num_remaining_sink_ids_(0) {
304}
305
306WebrtcAudioPrivateSetActiveSinkFunction::
307~WebrtcAudioPrivateSetActiveSinkFunction() {
308}
309
310bool 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
324void 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
343void 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
373void 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
383void WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread() {
384  SendResponse(true);
385}
386
387WebrtcAudioPrivateGetAssociatedSinkFunction::
388WebrtcAudioPrivateGetAssociatedSinkFunction() {
389}
390
391WebrtcAudioPrivateGetAssociatedSinkFunction::
392~WebrtcAudioPrivateGetAssociatedSinkFunction() {
393}
394
395bool 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
410void 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
422void
423WebrtcAudioPrivateGetAssociatedSinkFunction::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
455void
456WebrtcAudioPrivateGetAssociatedSinkFunction::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
470void 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