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 "base/json/json_writer.h"
      6 #include "base/strings/string_util.h"
      7 #include "base/strings/stringprintf.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "base/synchronization/waitable_event.h"
     10 #include "base/threading/platform_thread.h"
     11 #include "base/time/time.h"
     12 #include "chrome/browser/browser_process.h"
     13 #include "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
     14 #include "chrome/browser/extensions/component_loader.h"
     15 #include "chrome/browser/extensions/extension_apitest.h"
     16 #include "chrome/browser/extensions/extension_function_test_utils.h"
     17 #include "chrome/browser/extensions/extension_tab_util.h"
     18 #include "chrome/browser/media/webrtc_log_uploader.h"
     19 #include "chrome/browser/ui/browser.h"
     20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     21 #include "chrome/test/base/in_process_browser_test.h"
     22 #include "chrome/test/base/ui_test_utils.h"
     23 #include "content/public/browser/browser_thread.h"
     24 #include "content/public/browser/media_device_id.h"
     25 #include "content/public/browser/web_contents.h"
     26 #include "content/public/test/browser_test_utils.h"
     27 #include "extensions/common/permissions/permission_set.h"
     28 #include "extensions/common/permissions/permissions_data.h"
     29 #include "media/audio/audio_manager.h"
     30 #include "media/audio/audio_manager_base.h"
     31 #include "net/test/embedded_test_server/embedded_test_server.h"
     32 #include "testing/gtest/include/gtest/gtest.h"
     33 
     34 using base::JSONWriter;
     35 using content::RenderViewHost;
     36 using content::WebContents;
     37 using media::AudioDeviceNames;
     38 using media::AudioManager;
     39 
     40 namespace extensions {
     41 
     42 using extension_function_test_utils::RunFunctionAndReturnError;
     43 using extension_function_test_utils::RunFunctionAndReturnSingleResult;
     44 
     45 class AudioWaitingExtensionTest : public ExtensionApiTest {
     46  protected:
     47   void WaitUntilAudioIsPlaying(WebContents* tab) {
     48     // Wait for audio to start playing. We gate this on there being one
     49     // or more AudioOutputController objects for our tab.
     50     bool audio_playing = false;
     51     for (size_t remaining_tries = 50; remaining_tries > 0; --remaining_tries) {
     52       tab->GetRenderViewHost()->GetAudioOutputControllers(
     53           base::Bind(OnAudioControllers, &audio_playing));
     54       base::MessageLoop::current()->RunUntilIdle();
     55       if (audio_playing)
     56         break;
     57 
     58       base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
     59     }
     60 
     61     if (!audio_playing)
     62       FAIL() << "Audio did not start playing within ~5 seconds.";
     63   }
     64 
     65   // Used by the test above to wait until audio is playing.
     66   static void OnAudioControllers(
     67       bool* audio_playing,
     68       const RenderViewHost::AudioOutputControllerList& list) {
     69     if (!list.empty())
     70       *audio_playing = true;
     71   }
     72 };
     73 
     74 class WebrtcAudioPrivateTest : public AudioWaitingExtensionTest {
     75  public:
     76   WebrtcAudioPrivateTest()
     77       : enumeration_event_(false, false) {
     78   }
     79 
     80   virtual void SetUpOnMainThread() OVERRIDE {
     81     AudioWaitingExtensionTest::SetUpOnMainThread();
     82     // Needs to happen after chrome's schemes are added.
     83     source_url_ = GURL("chrome-extension://fakeid012345678/fakepage.html");
     84   }
     85 
     86  protected:
     87   std::string InvokeGetActiveSink(int tab_id) {
     88     base::ListValue parameters;
     89     parameters.AppendInteger(tab_id);
     90     std::string parameter_string;
     91     JSONWriter::Write(&parameters, &parameter_string);
     92 
     93     scoped_refptr<WebrtcAudioPrivateGetActiveSinkFunction> function =
     94         new WebrtcAudioPrivateGetActiveSinkFunction();
     95     function->set_source_url(source_url_);
     96     scoped_ptr<base::Value> result(
     97         RunFunctionAndReturnSingleResult(function.get(),
     98                                          parameter_string,
     99                                          browser()));
    100     std::string device_id;
    101     result->GetAsString(&device_id);
    102     return device_id;
    103   }
    104 
    105   scoped_ptr<base::Value> InvokeGetSinks(base::ListValue** sink_list) {
    106     scoped_refptr<WebrtcAudioPrivateGetSinksFunction> function =
    107         new WebrtcAudioPrivateGetSinksFunction();
    108     function->set_source_url(source_url_);
    109 
    110     scoped_ptr<base::Value> result(
    111         RunFunctionAndReturnSingleResult(function.get(), "[]", browser()));
    112     result->GetAsList(sink_list);
    113     return result.Pass();
    114   }
    115 
    116   // Synchronously (from the calling thread's point of view) runs the
    117   // given enumeration function on the device thread. On return,
    118   // |device_names| has been filled with the device names resulting
    119   // from that call.
    120   void GetAudioDeviceNames(
    121       void (AudioManager::*EnumerationFunc)(AudioDeviceNames*),
    122       AudioDeviceNames* device_names) {
    123     AudioManager* audio_manager = AudioManager::Get();
    124 
    125     if (!audio_manager->GetWorkerTaskRunner()->BelongsToCurrentThread()) {
    126       audio_manager->GetWorkerTaskRunner()->PostTask(
    127           FROM_HERE,
    128           base::Bind(&WebrtcAudioPrivateTest::GetAudioDeviceNames, this,
    129                      EnumerationFunc, device_names));
    130       enumeration_event_.Wait();
    131     } else {
    132       (audio_manager->*EnumerationFunc)(device_names);
    133       enumeration_event_.Signal();
    134     }
    135   }
    136 
    137   // Synchronously (from the calling thread's point of view) retrieve the
    138   // device id in the |origin| on the IO thread. On return,
    139   // |id_in_origin| contains the id |raw_device_id| is known by in
    140   // the origin.
    141   void GetIDInOrigin(content::ResourceContext* resource_context,
    142                      GURL origin,
    143                      const std::string& raw_device_id,
    144                      std::string* id_in_origin) {
    145     if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)) {
    146       content::BrowserThread::PostTask(
    147           content::BrowserThread::IO, FROM_HERE,
    148           base::Bind(&WebrtcAudioPrivateTest::GetIDInOrigin,
    149                      this, resource_context, origin, raw_device_id,
    150                      id_in_origin));
    151       enumeration_event_.Wait();
    152     } else {
    153       *id_in_origin = content::GetHMACForMediaDeviceID(
    154           resource_context->GetMediaDeviceIDSalt(),
    155           origin,
    156           raw_device_id);
    157       enumeration_event_.Signal();
    158     }
    159   }
    160 
    161   // Event used to signal completion of enumeration.
    162   base::WaitableEvent enumeration_event_;
    163 
    164   GURL source_url_;
    165 };
    166 
    167 #if !defined(OS_MACOSX)
    168 // http://crbug.com/334579
    169 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetSinks) {
    170   AudioDeviceNames devices;
    171   GetAudioDeviceNames(&AudioManager::GetAudioOutputDeviceNames, &devices);
    172 
    173   base::ListValue* sink_list = NULL;
    174   scoped_ptr<base::Value> result = InvokeGetSinks(&sink_list);
    175 
    176   std::string result_string;
    177   JSONWriter::Write(result.get(), &result_string);
    178   VLOG(2) << result_string;
    179 
    180   EXPECT_EQ(devices.size(), sink_list->GetSize());
    181 
    182   // Iterate through both lists in lockstep and compare. The order
    183   // should be identical.
    184   size_t ix = 0;
    185   AudioDeviceNames::const_iterator it = devices.begin();
    186   for (; ix < sink_list->GetSize() && it != devices.end();
    187        ++ix, ++it) {
    188     base::DictionaryValue* dict = NULL;
    189     sink_list->GetDictionary(ix, &dict);
    190     std::string sink_id;
    191     dict->GetString("sinkId", &sink_id);
    192 
    193     std::string expected_id;
    194     if (it->unique_id.empty() ||
    195         it->unique_id == media::AudioManagerBase::kDefaultDeviceId) {
    196       expected_id = media::AudioManagerBase::kDefaultDeviceId;
    197     } else {
    198       GetIDInOrigin(profile()->GetResourceContext(),
    199                     source_url_.GetOrigin(),
    200                     it->unique_id,
    201                     &expected_id);
    202     }
    203 
    204     EXPECT_EQ(expected_id, sink_id);
    205     std::string sink_label;
    206     dict->GetString("sinkLabel", &sink_label);
    207     EXPECT_EQ(it->device_name, sink_label);
    208 
    209     // TODO(joi): Verify the contents of these once we start actually
    210     // filling them in.
    211     EXPECT_TRUE(dict->HasKey("isDefault"));
    212     EXPECT_TRUE(dict->HasKey("isReady"));
    213     EXPECT_TRUE(dict->HasKey("sampleRate"));
    214   }
    215 }
    216 #endif  // OS_MACOSX
    217 
    218 // This exercises the case where you have a tab with no active media
    219 // stream and try to retrieve the currently active audio sink.
    220 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetActiveSinkNoMediaStream) {
    221   WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    222   int tab_id = ExtensionTabUtil::GetTabId(tab);
    223   base::ListValue parameters;
    224   parameters.AppendInteger(tab_id);
    225   std::string parameter_string;
    226   JSONWriter::Write(&parameters, &parameter_string);
    227 
    228   scoped_refptr<WebrtcAudioPrivateGetActiveSinkFunction> function =
    229       new WebrtcAudioPrivateGetActiveSinkFunction();
    230   function->set_source_url(source_url_);
    231   scoped_ptr<base::Value> result(
    232       RunFunctionAndReturnSingleResult(function.get(),
    233                                        parameter_string,
    234                                        browser()));
    235 
    236   std::string result_string;
    237   JSONWriter::Write(result.get(), &result_string);
    238   EXPECT_EQ("\"\"", result_string);
    239 }
    240 
    241 // This exercises the case where you have a tab with no active media
    242 // stream and try to set the audio sink.
    243 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, SetActiveSinkNoMediaStream) {
    244   WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    245   int tab_id = ExtensionTabUtil::GetTabId(tab);
    246   base::ListValue parameters;
    247   parameters.AppendInteger(tab_id);
    248   parameters.AppendString("no such id");
    249   std::string parameter_string;
    250   JSONWriter::Write(&parameters, &parameter_string);
    251 
    252   scoped_refptr<WebrtcAudioPrivateSetActiveSinkFunction> function =
    253       new WebrtcAudioPrivateSetActiveSinkFunction();
    254   function->set_source_url(source_url_);
    255   std::string error(RunFunctionAndReturnError(function.get(),
    256                                               parameter_string,
    257                                               browser()));
    258   EXPECT_EQ(base::StringPrintf("No active stream for tab with id: %d.", tab_id),
    259             error);
    260 }
    261 
    262 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetAndSetWithMediaStream) {
    263   // First retrieve the list of all sinks, so that we can run a test
    264   // where we set the active sink to each of the different available
    265   // sinks in turn.
    266   base::ListValue* sink_list = NULL;
    267   scoped_ptr<base::Value> result = InvokeGetSinks(&sink_list);
    268 
    269   ASSERT_TRUE(StartEmbeddedTestServer());
    270 
    271   // Open a normal page that uses an audio sink.
    272   ui_test_utils::NavigateToURL(
    273       browser(),
    274       GURL(embedded_test_server()->GetURL("/extensions/loop_audio.html")));
    275 
    276   WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    277   int tab_id = ExtensionTabUtil::GetTabId(tab);
    278 
    279   WaitUntilAudioIsPlaying(tab);
    280 
    281   std::string current_device = InvokeGetActiveSink(tab_id);
    282   VLOG(2) << "Before setting, current device: " << current_device;
    283   EXPECT_NE("", current_device);
    284 
    285   // Set to each of the other devices in turn.
    286   for (size_t ix = 0; ix < sink_list->GetSize(); ++ix) {
    287     base::DictionaryValue* dict = NULL;
    288     sink_list->GetDictionary(ix, &dict);
    289     std::string target_device;
    290     dict->GetString("sinkId", &target_device);
    291 
    292     base::ListValue parameters;
    293     parameters.AppendInteger(tab_id);
    294     parameters.AppendString(target_device);
    295     std::string parameter_string;
    296     JSONWriter::Write(&parameters, &parameter_string);
    297 
    298     scoped_refptr<WebrtcAudioPrivateSetActiveSinkFunction> function =
    299       new WebrtcAudioPrivateSetActiveSinkFunction();
    300     function->set_source_url(source_url_);
    301     scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
    302         function.get(), parameter_string, browser()));
    303     // The function was successful if the above invocation doesn't
    304     // fail. Just for kicks, also check that it returns no result.
    305     EXPECT_EQ(NULL, result.get());
    306 
    307     current_device = InvokeGetActiveSink(tab_id);
    308     VLOG(2) << "After setting to " << target_device
    309             << ", current device is " << current_device;
    310     EXPECT_EQ(target_device, current_device);
    311   }
    312 }
    313 
    314 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetAssociatedSink) {
    315   // Get the list of input devices. We can cheat in the unit test and
    316   // run this on the main thread since nobody else will be running at
    317   // the same time.
    318   AudioDeviceNames devices;
    319   GetAudioDeviceNames(&AudioManager::GetAudioInputDeviceNames, &devices);
    320 
    321   // Try to get an associated sink for each source.
    322   for (AudioDeviceNames::const_iterator device = devices.begin();
    323        device != devices.end();
    324        ++device) {
    325     scoped_refptr<WebrtcAudioPrivateGetAssociatedSinkFunction> function =
    326         new WebrtcAudioPrivateGetAssociatedSinkFunction();
    327     function->set_source_url(source_url_);
    328 
    329     std::string raw_device_id = device->unique_id;
    330     VLOG(2) << "Trying to find associated sink for device " << raw_device_id;
    331     std::string source_id_in_origin;
    332     GURL origin(GURL("http://www.google.com/").GetOrigin());
    333     GetIDInOrigin(profile()->GetResourceContext(),
    334                   origin,
    335                   raw_device_id,
    336                   &source_id_in_origin);
    337 
    338     base::ListValue parameters;
    339     parameters.AppendString(origin.spec());
    340     parameters.AppendString(source_id_in_origin);
    341     std::string parameter_string;
    342     JSONWriter::Write(&parameters, &parameter_string);
    343 
    344     scoped_ptr<base::Value> result(
    345         RunFunctionAndReturnSingleResult(function.get(),
    346                                          parameter_string,
    347                                          browser()));
    348     std::string result_string;
    349     JSONWriter::Write(result.get(), &result_string);
    350     VLOG(2) << "Results: " << result_string;
    351   }
    352 }
    353 
    354 IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, TriggerEvent) {
    355   WebrtcAudioPrivateEventService* service =
    356       WebrtcAudioPrivateEventService::GetFactoryInstance()->Get(profile());
    357 
    358   // Just trigger, without any extension listening.
    359   service->OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
    360 
    361   // Now load our test extension and do it again.
    362   const extensions::Extension* extension = LoadExtension(
    363       test_data_dir_.AppendASCII("webrtc_audio_private_event_listener"));
    364   service->OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
    365 
    366   // Check that the extension got the notification.
    367   std::string result = ExecuteScriptInBackgroundPage(extension->id(),
    368                                                      "reportIfGot()");
    369   EXPECT_EQ("true", result);
    370 }
    371 
    372 class HangoutServicesBrowserTest : public AudioWaitingExtensionTest {
    373  public:
    374   virtual void SetUp() OVERRIDE {
    375     // Make sure the Hangout Services component extension gets loaded.
    376     ComponentLoader::EnableBackgroundExtensionsForTesting();
    377     AudioWaitingExtensionTest::SetUp();
    378   }
    379 };
    380 
    381 #if defined(GOOGLE_CHROME_BUILD) || defined(ENABLE_HANGOUT_SERVICES_EXTENSION)
    382 IN_PROC_BROWSER_TEST_F(HangoutServicesBrowserTest,
    383                        RunComponentExtensionTest) {
    384   // This runs the end-to-end JavaScript test for the Hangout Services
    385   // component extension, which uses the webrtcAudioPrivate API among
    386   // others.
    387   ASSERT_TRUE(StartEmbeddedTestServer());
    388   GURL url(embedded_test_server()->GetURL(
    389                "/extensions/hangout_services_test.html"));
    390   // The "externally connectable" extension permission doesn't seem to
    391   // like when we use 127.0.0.1 as the host, but using localhost works.
    392   std::string url_spec = url.spec();
    393   ReplaceFirstSubstringAfterOffset(&url_spec, 0, "127.0.0.1", "localhost");
    394   GURL localhost_url(url_spec);
    395   ui_test_utils::NavigateToURL(browser(), localhost_url);
    396 
    397   WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    398   WaitUntilAudioIsPlaying(tab);
    399 
    400   // Override, i.e. disable, uploading. We don't want to try sending data to
    401   // servers when running the test. We don't bother about the contents of the
    402   // buffer |dummy|, that's tested in other tests.
    403   std::string dummy;
    404   g_browser_process->webrtc_log_uploader()->
    405       OverrideUploadWithBufferForTesting(&dummy);
    406 
    407   ASSERT_TRUE(content::ExecuteScript(tab, "browsertestRunAllTests();"));
    408 
    409   content::TitleWatcher title_watcher(tab, base::ASCIIToUTF16("success"));
    410   title_watcher.AlsoWaitForTitle(base::ASCIIToUTF16("failure"));
    411   base::string16 result = title_watcher.WaitAndGetTitle();
    412   EXPECT_EQ(base::ASCIIToUTF16("success"), result);
    413 
    414   g_browser_process->webrtc_log_uploader()->OverrideUploadWithBufferForTesting(
    415       NULL);
    416 }
    417 #endif  // defined(GOOGLE_CHROME_BUILD) || defined(ENABLE_HANGOUT_SERVICES_EXTENSION)
    418 
    419 }  // namespace extensions
    420