text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <list>
  
  #include "base/bind.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/run_loop.h"
  #include "base/strings/utf_string_conversions.h"
  #include "content/browser/speech/google_streaming_remote_engine.h"
  #include "content/browser/speech/speech_recognition_manager_impl.h"
  #include "content/browser/speech/speech_recognizer_impl.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/notification_types.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/test/browser_test_utils.h"
  #include "content/public/test/test_utils.h"
  #include "content/shell/browser/shell.h"
  #include "content/test/content_browser_test.h"
  #include "content/test/content_browser_test_utils.h"
  #include "content/test/mock_google_streaming_server.h"
  #include "media/audio/mock_audio_manager.h"
  #include "media/audio/test_audio_input_controller_factory.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  using base::RunLoop;
  
  namespace content {
  
  class SpeechRecognitionBrowserTest :
      public ContentBrowserTest,
      public MockGoogleStreamingServer::Delegate,
      public media::TestAudioInputControllerDelegate {
   public:
    enum StreamingServerState {
      kIdle,
      kTestAudioControllerOpened,
      kClientConnected,
      kClientAudioUpload,
      kClientAudioUploadComplete,
      kTestAudioControllerClosed,
      kClientDisconnected
    };
  
    // MockGoogleStreamingServerDelegate methods.
    virtual void OnClientConnected() OVERRIDE {
      ASSERT_EQ(kTestAudioControllerOpened, streaming_server_state_);
      streaming_server_state_ = kClientConnected;
    }
  
    virtual void OnClientAudioUpload() OVERRIDE {
      if (streaming_server_state_ == kClientConnected)
        streaming_server_state_ = kClientAudioUpload;
    }
  
    virtual void OnClientAudioUploadComplete() OVERRIDE {
      ASSERT_EQ(kTestAudioControllerClosed, streaming_server_state_);
      streaming_server_state_ = kClientAudioUploadComplete;
    }
  
    virtual void OnClientDisconnected() OVERRIDE {
      ASSERT_EQ(kClientAudioUploadComplete, streaming_server_state_);
      streaming_server_state_ = kClientDisconnected;
    }
  
    // media::TestAudioInputControllerDelegate methods.
    virtual void TestAudioControllerOpened(
        media::TestAudioInputController* controller) OVERRIDE {
      ASSERT_EQ(kIdle, streaming_server_state_);
      streaming_server_state_ = kTestAudioControllerOpened;
      const int capture_packet_interval_ms =
          (1000 * controller->audio_parameters().frames_per_buffer()) /
          controller->audio_parameters().sample_rate();
      ASSERT_EQ(GoogleStreamingRemoteEngine::kAudioPacketIntervalMs,
          capture_packet_interval_ms);
      FeedAudioController(500 /* ms */, /*noise=*/ false);
      FeedAudioController(1000 /* ms */, /*noise=*/ true);
      FeedAudioController(1000 /* ms */, /*noise=*/ false);
    }
  
    virtual void TestAudioControllerClosed(
        media::TestAudioInputController* controller) OVERRIDE {
      ASSERT_EQ(kClientAudioUpload, streaming_server_state_);
      streaming_server_state_ = kTestAudioControllerClosed;
      mock_streaming_server_->MockGoogleStreamingServer::SimulateResult(
          GetGoodSpeechResult());
    }
  
    // Helper methods used by test fixtures.
    GURL GetTestUrlFromFragment(const std::string fragment) {
      return GURL(GetTestUrl("speech", "web_speech_recognition.html").spec() +
          "#" + fragment);
    }
  
    std::string GetPageFragment() {
      return shell()->web_contents()->GetURL().ref();
    }
  
    const StreamingServerState &streaming_server_state() {
      return streaming_server_state_;
    }
  
   protected:
    // ContentBrowserTest methods.
    virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
      test_audio_input_controller_factory_.set_delegate(this);
      media::AudioInputController::set_factory_for_testing(
          &test_audio_input_controller_factory_);
      mock_streaming_server_.reset(new MockGoogleStreamingServer(this));
      streaming_server_state_ = kIdle;
    }
  
    virtual void SetUpOnMainThread() OVERRIDE {
      ASSERT_TRUE(SpeechRecognitionManagerImpl::GetInstance());
      SpeechRecognizerImpl::SetAudioManagerForTesting(
          new media::MockAudioManager(BrowserThread::GetMessageLoopProxyForThread(
              BrowserThread::IO)));
    }
  
    virtual void TearDownOnMainThread() OVERRIDE {
      SpeechRecognizerImpl::SetAudioManagerForTesting(NULL);
    }
  
    virtual void TearDownInProcessBrowserTestFixture() OVERRIDE {
      test_audio_input_controller_factory_.set_delegate(NULL);
      mock_streaming_server_.reset();
    }
  
   private:
    static void FeedSingleBufferToAudioController(
        scoped_refptr<media::TestAudioInputController> controller,
        size_t buffer_size,
        bool fill_with_noise) {
      DCHECK(controller.get());
      scoped_ptr<uint8[]> audio_buffer(new uint8[buffer_size]);
      if (fill_with_noise) {
        for (size_t i = 0; i < buffer_size; ++i)
          audio_buffer[i] = static_cast<uint8>(127 * sin(i * 3.14F /
              (16 * buffer_size)));
      } else {
        memset(audio_buffer.get(), 0, buffer_size);
      }
      controller->event_handler()->OnData(controller,
                                          audio_buffer.get(),
                                          buffer_size);
    }
  
    void FeedAudioController(int duration_ms, bool feed_with_noise) {
      media::TestAudioInputController* controller =
          test_audio_input_controller_factory_.controller();
      ASSERT_TRUE(controller);
      const media::AudioParameters& audio_params = controller->audio_parameters();
      const size_t buffer_size = audio_params.GetBytesPerBuffer();
      const int ms_per_buffer = audio_params.frames_per_buffer() * 1000 /
                                audio_params.sample_rate();
      // We can only simulate durations that are integer multiples of the
      // buffer size. In this regard see
      // SpeechRecognitionEngine::GetDesiredAudioChunkDurationMs().
      ASSERT_EQ(0, duration_ms % ms_per_buffer);
  
      const int n_buffers = duration_ms / ms_per_buffer;
      for (int i = 0; i < n_buffers; ++i) {
        base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
            &FeedSingleBufferToAudioController,
            scoped_refptr<media::TestAudioInputController>(controller),
            buffer_size,
            feed_with_noise));
      }
    }
  
    SpeechRecognitionResult GetGoodSpeechResult() {
      SpeechRecognitionResult result;
      result.hypotheses.push_back(SpeechRecognitionHypothesis(
          UTF8ToUTF16("Pictures of the moon"), 1.0F));
      return result;
    }
  
    StreamingServerState streaming_server_state_;
    scoped_ptr<MockGoogleStreamingServer> mock_streaming_server_;
    media::TestAudioInputControllerFactory test_audio_input_controller_factory_;
  };
  
  // Simply loads the test page and checks if it was able to create a Speech
  // Recognition object in JavaScript, to make sure the Web Speech API is enabled.
  IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, Precheck) {
    NavigateToURLBlockUntilNavigationsComplete(
        shell(), GetTestUrlFromFragment("precheck"), 2);
  
    EXPECT_EQ(kIdle, streaming_server_state());
    EXPECT_EQ("success", GetPageFragment());
  }
  
  IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, OneShotRecognition) {
    NavigateToURLBlockUntilNavigationsComplete(
        shell(), GetTestUrlFromFragment("oneshot"), 2);
  
    EXPECT_EQ(kClientDisconnected, streaming_server_state());
    EXPECT_EQ("goodresult1", GetPageFragment());
  }
  
  }  // namespace content
  