Home | History | Annotate | Download | only in webrtc_audio_private
      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 
     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 
     46 WebrtcAudioPrivateEventService::~WebrtcAudioPrivateEventService() {
     47 }
     48 
     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>*
     58 WebrtcAudioPrivateEventService::GetFactoryInstance() {
     59   return g_factory.Pointer();
     60 }
     61 
     62 // static
     63 const char* WebrtcAudioPrivateEventService::service_name() {
     64   return "WebrtcAudioPrivateEventService";
     65 }
     66 
     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 
     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 
    102 WebrtcAudioPrivateFunction::WebrtcAudioPrivateFunction()
    103     : resource_context_(NULL) {
    104 }
    105 
    106 WebrtcAudioPrivateFunction::~WebrtcAudioPrivateFunction() {
    107 }
    108 
    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 
    131 void WebrtcAudioPrivateFunction::OnOutputDeviceNames(
    132     scoped_ptr<AudioDeviceNames> device_names) {
    133   NOTREACHED();
    134 }
    135 
    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 
    155 void WebrtcAudioPrivateFunction::OnControllerList(
    156     const content::RenderViewHost::AudioOutputControllerList& list) {
    157   NOTREACHED();
    158 }
    159 
    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 
    176 void WebrtcAudioPrivateFunction::OnHMACCalculated(const std::string& hmac) {
    177   NOTREACHED();
    178 }
    179 
    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 
    199 void WebrtcAudioPrivateFunction::InitResourceContext() {
    200   resource_context_ = GetProfile()->GetResourceContext();
    201 }
    202 
    203 content::ResourceContext* WebrtcAudioPrivateFunction::resource_context() const {
    204   DCHECK(resource_context_);  // Did you forget to InitResourceContext()?
    205   return resource_context_;
    206 }
    207 
    208 bool WebrtcAudioPrivateGetSinksFunction::RunAsync() {
    209   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    210 
    211   InitResourceContext();
    212   GetOutputDeviceNames();
    213 
    214   return true;
    215 }
    216 
    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 
    248 void WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread() {
    249   SendResponse(true);
    250 }
    251 
    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 
    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 
    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::
    301 WebrtcAudioPrivateSetActiveSinkFunction()
    302     : tab_id_(0),
    303       num_remaining_sink_ids_(0) {
    304 }
    305 
    306 WebrtcAudioPrivateSetActiveSinkFunction::
    307 ~WebrtcAudioPrivateSetActiveSinkFunction() {
    308 }
    309 
    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 
    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 
    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 
    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 
    383 void WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread() {
    384   SendResponse(true);
    385 }
    386 
    387 WebrtcAudioPrivateGetAssociatedSinkFunction::
    388 WebrtcAudioPrivateGetAssociatedSinkFunction() {
    389 }
    390 
    391 WebrtcAudioPrivateGetAssociatedSinkFunction::
    392 ~WebrtcAudioPrivateGetAssociatedSinkFunction() {
    393 }
    394 
    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 
    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
    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
    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 
    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