Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / api / webrtc_audio_private / webrtc_audio_private_api.cc
blobd56ed0ca2eac134e953bc1dbca79f75536072ec9
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.
5 #include "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
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_tab_util.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "content/public/browser/media_device_id.h"
14 #include "content/public/browser/resource_context.h"
15 #include "content/public/browser/web_contents.h"
16 #include "extensions/browser/event_router.h"
17 #include "extensions/browser/extension_registry.h"
18 #include "extensions/common/error_utils.h"
19 #include "extensions/common/permissions/permissions_data.h"
20 #include "media/audio/audio_manager_base.h"
21 #include "media/audio/audio_output_controller.h"
23 namespace extensions {
25 using content::BrowserThread;
26 using content::RenderProcessHost;
27 using media::AudioDeviceNames;
28 using media::AudioManager;
30 namespace wap = api::webrtc_audio_private;
32 using api::webrtc_audio_private::RequestInfo;
34 static base::LazyInstance<
35 BrowserContextKeyedAPIFactory<WebrtcAudioPrivateEventService> > g_factory =
36 LAZY_INSTANCE_INITIALIZER;
38 WebrtcAudioPrivateEventService::WebrtcAudioPrivateEventService(
39 content::BrowserContext* context)
40 : browser_context_(context) {
41 // In unit tests, the SystemMonitor may not be created.
42 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
43 if (system_monitor)
44 system_monitor->AddDevicesChangedObserver(this);
47 WebrtcAudioPrivateEventService::~WebrtcAudioPrivateEventService() {
50 void WebrtcAudioPrivateEventService::Shutdown() {
51 // In unit tests, the SystemMonitor may not be created.
52 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
53 if (system_monitor)
54 system_monitor->RemoveDevicesChangedObserver(this);
57 // static
58 BrowserContextKeyedAPIFactory<WebrtcAudioPrivateEventService>*
59 WebrtcAudioPrivateEventService::GetFactoryInstance() {
60 return g_factory.Pointer();
63 // static
64 const char* WebrtcAudioPrivateEventService::service_name() {
65 return "WebrtcAudioPrivateEventService";
68 void WebrtcAudioPrivateEventService::OnDevicesChanged(
69 base::SystemMonitor::DeviceType device_type) {
70 switch (device_type) {
71 case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE:
72 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
73 SignalEvent();
74 break;
76 default:
77 // No action needed.
78 break;
82 void WebrtcAudioPrivateEventService::SignalEvent() {
83 using api::webrtc_audio_private::OnSinksChanged::kEventName;
85 EventRouter* router = EventRouter::Get(browser_context_);
86 if (!router || !router->HasEventListener(kEventName))
87 return;
89 for (const scoped_refptr<const extensions::Extension>& extension :
90 ExtensionRegistry::Get(browser_context_)->enabled_extensions()) {
91 const std::string& extension_id = extension->id();
92 if (router->ExtensionHasEventListener(extension_id, kEventName) &&
93 extension->permissions_data()->HasAPIPermission("webrtcAudioPrivate")) {
94 scoped_ptr<Event> event(
95 new Event(events::WEBRTC_AUDIO_PRIVATE_ON_SINKS_CHANGED, kEventName,
96 make_scoped_ptr(new base::ListValue()).Pass()));
97 router->DispatchEventToExtension(extension_id, event.Pass());
102 WebrtcAudioPrivateFunction::WebrtcAudioPrivateFunction()
103 : resource_context_(NULL) {
106 WebrtcAudioPrivateFunction::~WebrtcAudioPrivateFunction() {
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;
120 scoped_ptr<AudioDeviceNames> device_names(new AudioDeviceNames);
121 AudioManager::Get()->GetAudioOutputDeviceNames(device_names.get());
123 BrowserThread::PostTask(
124 BrowserThread::IO,
125 FROM_HERE,
126 base::Bind(&WebrtcAudioPrivateFunction::OnOutputDeviceNames,
127 this,
128 Passed(&device_names)));
131 void WebrtcAudioPrivateFunction::OnOutputDeviceNames(
132 scoped_ptr<AudioDeviceNames> device_names) {
133 NOTREACHED();
136 bool WebrtcAudioPrivateFunction::GetControllerList(const RequestInfo& request) {
137 content::RenderProcessHost* rph = nullptr;
139 // If |guest_process_id| is defined, directly use this id to find the
140 // corresponding RenderProcessHost.
141 if (request.guest_process_id.get()) {
142 rph = content::RenderProcessHost::FromID(*request.guest_process_id.get());
143 } else if (request.tab_id.get()) {
144 int tab_id = *request.tab_id.get();
145 content::WebContents* contents = NULL;
146 if (!ExtensionTabUtil::GetTabById(tab_id, GetProfile(), true, NULL, NULL,
147 &contents, NULL)) {
148 error_ = extensions::ErrorUtils::FormatErrorMessage(
149 extensions::tabs_constants::kTabNotFoundError,
150 base::IntToString(tab_id));
151 return false;
153 rph = contents->GetRenderProcessHost();
154 } else {
155 return false;
158 if (!rph)
159 return false;
161 rph->GetAudioOutputControllers(
162 base::Bind(&WebrtcAudioPrivateFunction::OnControllerList, this));
163 return true;
166 void WebrtcAudioPrivateFunction::OnControllerList(
167 const content::RenderProcessHost::AudioOutputControllerList& list) {
168 NOTREACHED();
171 void WebrtcAudioPrivateFunction::CalculateHMAC(const std::string& raw_id) {
172 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
173 BrowserThread::PostTask(
174 BrowserThread::IO,
175 FROM_HERE,
176 base::Bind(&WebrtcAudioPrivateFunction::CalculateHMAC, this, raw_id));
177 return;
180 std::string hmac = CalculateHMACImpl(raw_id);
181 BrowserThread::PostTask(
182 BrowserThread::UI,
183 FROM_HERE,
184 base::Bind(&WebrtcAudioPrivateFunction::OnHMACCalculated, this, hmac));
187 void WebrtcAudioPrivateFunction::OnHMACCalculated(const std::string& hmac) {
188 NOTREACHED();
191 std::string WebrtcAudioPrivateFunction::CalculateHMACImpl(
192 const std::string& raw_id) {
193 DCHECK_CURRENTLY_ON(BrowserThread::IO);
195 // We don't hash the default device name, and we always return
196 // "default" for the default device. There is code in SetActiveSink
197 // that transforms "default" to the empty string, and code in
198 // GetActiveSink that ensures we return "default" if we get the
199 // empty string as the current device ID.
200 if (raw_id.empty() || raw_id == media::AudioManagerBase::kDefaultDeviceId)
201 return media::AudioManagerBase::kDefaultDeviceId;
203 GURL security_origin(source_url().GetOrigin());
204 return content::GetHMACForMediaDeviceID(
205 resource_context()->GetMediaDeviceIDSalt(),
206 security_origin,
207 raw_id);
210 void WebrtcAudioPrivateFunction::InitResourceContext() {
211 resource_context_ = GetProfile()->GetResourceContext();
214 content::ResourceContext* WebrtcAudioPrivateFunction::resource_context() const {
215 DCHECK(resource_context_); // Did you forget to InitResourceContext()?
216 return resource_context_;
219 bool WebrtcAudioPrivateGetSinksFunction::RunAsync() {
220 DCHECK_CURRENTLY_ON(BrowserThread::UI);
222 InitResourceContext();
223 GetOutputDeviceNames();
225 return true;
228 void WebrtcAudioPrivateGetSinksFunction::OnOutputDeviceNames(
229 scoped_ptr<AudioDeviceNames> raw_ids) {
230 DCHECK_CURRENTLY_ON(BrowserThread::IO);
232 std::vector<linked_ptr<wap::SinkInfo> > results;
233 for (AudioDeviceNames::const_iterator it = raw_ids->begin();
234 it != raw_ids->end();
235 ++it) {
236 linked_ptr<wap::SinkInfo> info(new wap::SinkInfo);
237 info->sink_id = CalculateHMACImpl(it->unique_id);
238 info->sink_label = it->device_name;
239 // TODO(joi): Add other parameters.
240 results.push_back(info);
243 // It's safe to directly set the results here (from a thread other
244 // than the UI thread, on which an AsyncExtensionFunction otherwise
245 // normally runs) because there is one instance of this object per
246 // function call, no actor outside of this object is modifying the
247 // results_ member, and the different method invocations on this
248 // object run strictly in sequence; first RunAsync on the UI thread,
249 // then DoQuery on the audio IO thread, then DoneOnUIThread on the
250 // UI thread.
251 results_.reset(wap::GetSinks::Results::Create(results).release());
253 BrowserThread::PostTask(
254 BrowserThread::UI,
255 FROM_HERE,
256 base::Bind(&WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread, this));
259 void WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread() {
260 SendResponse(true);
263 bool WebrtcAudioPrivateGetActiveSinkFunction::RunAsync() {
264 DCHECK_CURRENTLY_ON(BrowserThread::UI);
265 InitResourceContext();
267 scoped_ptr<wap::GetActiveSink::Params> params(
268 wap::GetActiveSink::Params::Create(*args_));
269 EXTENSION_FUNCTION_VALIDATE(params.get());
271 return GetControllerList(params->request);
274 void WebrtcAudioPrivateGetActiveSinkFunction::OnControllerList(
275 const RenderProcessHost::AudioOutputControllerList& controllers) {
276 DCHECK_CURRENTLY_ON(BrowserThread::UI);
278 if (controllers.empty()) {
279 // If there is no current audio stream for the rvh, we return an
280 // empty string as the sink ID.
281 DVLOG(2) << "chrome.webrtcAudioPrivate.getActiveSink: No controllers.";
282 results_.reset(
283 wap::GetActiveSink::Results::Create(std::string()).release());
284 SendResponse(true);
285 } else {
286 DVLOG(2) << "chrome.webrtcAudioPrivate.getActiveSink: "
287 << controllers.size() << " controllers.";
288 // TODO(joi): Debug-only, DCHECK that all items have the same ID.
290 // Send the raw ID through CalculateHMAC, and send the result in
291 // OnHMACCalculated.
292 (*controllers.begin())->GetOutputDeviceId(
293 base::Bind(&WebrtcAudioPrivateGetActiveSinkFunction::CalculateHMAC,
294 this));
298 void WebrtcAudioPrivateGetActiveSinkFunction::OnHMACCalculated(
299 const std::string& hmac_id) {
300 DCHECK_CURRENTLY_ON(BrowserThread::UI);
302 std::string result = hmac_id;
303 if (result.empty()) {
304 DVLOG(2) << "Received empty ID, replacing with default ID.";
305 result = media::AudioManagerBase::kDefaultDeviceId;
307 results_.reset(wap::GetActiveSink::Results::Create(result).release());
308 SendResponse(true);
311 WebrtcAudioPrivateSetActiveSinkFunction::
312 WebrtcAudioPrivateSetActiveSinkFunction()
313 : num_remaining_sink_ids_(0) {
316 WebrtcAudioPrivateSetActiveSinkFunction::
317 ~WebrtcAudioPrivateSetActiveSinkFunction() {
320 bool WebrtcAudioPrivateSetActiveSinkFunction::RunAsync() {
321 DCHECK_CURRENTLY_ON(BrowserThread::UI);
322 scoped_ptr<wap::SetActiveSink::Params> params(
323 wap::SetActiveSink::Params::Create(*args_));
324 EXTENSION_FUNCTION_VALIDATE(params.get());
326 InitResourceContext();
328 if (params->request.guest_process_id.get()) {
329 request_info_.guest_process_id.reset(
330 new int(*params->request.guest_process_id.get()));
331 } else if (params->request.tab_id.get()) {
332 request_info_.tab_id.reset(new int(*params->request.tab_id.get()));
333 } else {
334 return false;
337 sink_id_ = params->sink_id;
339 return GetControllerList(request_info_);
342 void WebrtcAudioPrivateSetActiveSinkFunction::OnControllerList(
343 const RenderProcessHost::AudioOutputControllerList& controllers) {
344 DCHECK_CURRENTLY_ON(BrowserThread::UI);
346 std::string requested_process_type;
347 int requested_process_id;
348 if (request_info_.guest_process_id.get()) {
349 requested_process_type = "guestProcessId";
350 requested_process_id = *request_info_.guest_process_id.get();
351 } else {
352 requested_process_type = "tabId";
353 requested_process_id = *request_info_.tab_id.get();
356 controllers_ = controllers;
357 num_remaining_sink_ids_ = controllers_.size();
358 if (num_remaining_sink_ids_ == 0) {
359 error_ = extensions::ErrorUtils::FormatErrorMessage(
360 "No active stream for " + requested_process_type + " *",
361 base::IntToString(requested_process_id));
362 SendResponse(false);
363 } else {
364 // We need to get the output device names, and calculate the HMAC
365 // for each, to find the raw ID for the ID provided to this API
366 // function call.
367 GetOutputDeviceNames();
371 void WebrtcAudioPrivateSetActiveSinkFunction::OnOutputDeviceNames(
372 scoped_ptr<AudioDeviceNames> device_names) {
373 DCHECK_CURRENTLY_ON(BrowserThread::IO);
375 std::string raw_sink_id;
376 if (sink_id_ == media::AudioManagerBase::kDefaultDeviceId) {
377 DVLOG(2) << "Received default ID, replacing with empty ID.";
378 raw_sink_id = "";
379 } else {
380 for (AudioDeviceNames::const_iterator it = device_names->begin();
381 it != device_names->end();
382 ++it) {
383 if (sink_id_ == CalculateHMACImpl(it->unique_id)) {
384 raw_sink_id = it->unique_id;
385 break;
389 if (raw_sink_id.empty())
390 DVLOG(2) << "Found no matching raw sink ID for HMAC " << sink_id_;
393 RenderProcessHost::AudioOutputControllerList::const_iterator it =
394 controllers_.begin();
395 for (; it != controllers_.end(); ++it) {
396 (*it)->SwitchOutputDevice(raw_sink_id, base::Bind(
397 &WebrtcAudioPrivateSetActiveSinkFunction::SwitchDone, this));
401 void WebrtcAudioPrivateSetActiveSinkFunction::SwitchDone() {
402 if (--num_remaining_sink_ids_ == 0) {
403 BrowserThread::PostTask(
404 BrowserThread::UI,
405 FROM_HERE,
406 base::Bind(&WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread,
407 this));
411 void WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread() {
412 SendResponse(true);
415 WebrtcAudioPrivateGetAssociatedSinkFunction::
416 WebrtcAudioPrivateGetAssociatedSinkFunction() {
419 WebrtcAudioPrivateGetAssociatedSinkFunction::
420 ~WebrtcAudioPrivateGetAssociatedSinkFunction() {
423 bool WebrtcAudioPrivateGetAssociatedSinkFunction::RunAsync() {
424 params_ = wap::GetAssociatedSink::Params::Create(*args_);
425 DCHECK_CURRENTLY_ON(BrowserThread::UI);
426 EXTENSION_FUNCTION_VALIDATE(params_.get());
428 InitResourceContext();
430 AudioManager::Get()->GetWorkerTaskRunner()->PostTask(
431 FROM_HERE,
432 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
433 GetDevicesOnDeviceThread, this));
435 return true;
438 void WebrtcAudioPrivateGetAssociatedSinkFunction::GetDevicesOnDeviceThread() {
439 DCHECK(AudioManager::Get()->GetWorkerTaskRunner()->BelongsToCurrentThread());
440 AudioManager::Get()->GetAudioInputDeviceNames(&source_devices_);
442 BrowserThread::PostTask(
443 BrowserThread::IO,
444 FROM_HERE,
445 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
446 GetRawSourceIDOnIOThread,
447 this));
450 void
451 WebrtcAudioPrivateGetAssociatedSinkFunction::GetRawSourceIDOnIOThread() {
452 DCHECK_CURRENTLY_ON(BrowserThread::IO);
454 GURL security_origin(params_->security_origin);
455 std::string source_id_in_origin(params_->source_id_in_origin);
457 // Find the raw source ID for source_id_in_origin.
458 std::string raw_source_id;
459 for (AudioDeviceNames::const_iterator it = source_devices_.begin();
460 it != source_devices_.end();
461 ++it) {
462 const std::string& id = it->unique_id;
463 if (content::DoesMediaDeviceIDMatchHMAC(
464 resource_context()->GetMediaDeviceIDSalt(),
465 security_origin,
466 source_id_in_origin,
467 id)) {
468 raw_source_id = id;
469 DVLOG(2) << "Found raw ID " << raw_source_id
470 << " for source ID in origin " << source_id_in_origin;
471 break;
475 AudioManager::Get()->GetWorkerTaskRunner()->PostTask(
476 FROM_HERE,
477 base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
478 GetAssociatedSinkOnDeviceThread,
479 this,
480 raw_source_id));
483 void
484 WebrtcAudioPrivateGetAssociatedSinkFunction::GetAssociatedSinkOnDeviceThread(
485 const std::string& raw_source_id) {
486 DCHECK(AudioManager::Get()->GetWorkerTaskRunner()->BelongsToCurrentThread());
488 // We return an empty string if there is no associated output device.
489 std::string raw_sink_id;
490 if (!raw_source_id.empty()) {
491 raw_sink_id =
492 AudioManager::Get()->GetAssociatedOutputDeviceID(raw_source_id);
495 CalculateHMAC(raw_sink_id);
498 void WebrtcAudioPrivateGetAssociatedSinkFunction::OnHMACCalculated(
499 const std::string& associated_sink_id) {
500 DCHECK_CURRENTLY_ON(BrowserThread::UI);
502 if (associated_sink_id == media::AudioManagerBase::kDefaultDeviceId) {
503 DVLOG(2) << "Got default ID, replacing with empty ID.";
504 results_.reset(wap::GetAssociatedSink::Results::Create("").release());
505 } else {
506 results_.reset(
507 wap::GetAssociatedSink::Results::Create(associated_sink_id).release());
510 SendResponse(true);
513 } // namespace extensions