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