// Copyright (c) 2012 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 "content/browser/speech/speech_recognition_manager_impl.h"

#include "base/bind.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/speech/google_one_shot_remote_engine.h"
#include "content/browser/speech/google_streaming_remote_engine.h"
#include "content/browser/speech/speech_recognition_engine.h"
#include "content/browser/speech/speech_recognizer.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/speech_recognition_event_listener.h"
#include "content/public/browser/speech_recognition_manager_delegate.h"
#include "content/public/browser/speech_recognition_session_config.h"
#include "content/public/browser/speech_recognition_session_context.h"
#include "content/public/common/speech_recognition_error.h"
#include "content/public/common/speech_recognition_result.h"
#include "media/audio/audio_manager.h"

using base::Callback;

namespace content {

namespace {

SpeechRecognitionManagerImpl* g_speech_recognition_manager_impl;

void ShowAudioInputSettingsOnFileThread() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  media::AudioManager* audio_manager = BrowserMainLoop::GetAudioManager();
  DCHECK(audio_manager->CanShowAudioInputSettings());
  if (audio_manager->CanShowAudioInputSettings())
    audio_manager->ShowAudioInputSettings();
}

}  // namespace

SpeechRecognitionManager* SpeechRecognitionManager::GetInstance() {
  return SpeechRecognitionManagerImpl::GetInstance();
}

SpeechRecognitionManagerImpl* SpeechRecognitionManagerImpl::GetInstance() {
  return g_speech_recognition_manager_impl;
}

SpeechRecognitionManagerImpl::SpeechRecognitionManagerImpl()
    : primary_session_id_(kSessionIDInvalid),
      last_session_id_(kSessionIDInvalid),
      is_dispatching_event_(false),
      delegate_(GetContentClient()->browser()->
                    GetSpeechRecognitionManagerDelegate()),
      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
  DCHECK(!g_speech_recognition_manager_impl);
  g_speech_recognition_manager_impl = this;
}

SpeechRecognitionManagerImpl::~SpeechRecognitionManagerImpl() {
  DCHECK(g_speech_recognition_manager_impl);
  g_speech_recognition_manager_impl = NULL;
  // Recognition sessions will be aborted by the corresponding destructors.
  sessions_.clear();
}

int SpeechRecognitionManagerImpl::CreateSession(
    const SpeechRecognitionSessionConfig& config) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  const int session_id = GetNextSessionID();
  DCHECK(!SessionExists(session_id));
  // Set-up the new session.
  Session& session = sessions_[session_id];
  session.id = session_id;
  session.config = config;
  session.context = config.initial_context;

  std::string hardware_info;
  bool can_report_metrics = false;
  if (delegate_.get())
    delegate_->GetDiagnosticInformation(&can_report_metrics, &hardware_info);

  SpeechRecognitionEngineConfig remote_engine_config;
  remote_engine_config.language = config.language;
  remote_engine_config.grammars = config.grammars;
  remote_engine_config.audio_sample_rate = SpeechRecognizer::kAudioSampleRate;
  remote_engine_config.audio_num_bits_per_sample =
     SpeechRecognizer::kNumBitsPerAudioSample;
  remote_engine_config.filter_profanities = config.filter_profanities;
  remote_engine_config.continuous = config.continuous;
  remote_engine_config.interim_results = config.interim_results;
  remote_engine_config.max_hypotheses = config.max_hypotheses;
  remote_engine_config.hardware_info = hardware_info;
  remote_engine_config.origin_url = can_report_metrics ? config.origin_url : "";

  SpeechRecognitionEngine* google_remote_engine;
  if (config.is_legacy_api) {
    google_remote_engine =
        new GoogleOneShotRemoteEngine(config.url_request_context_getter);
  } else {
    google_remote_engine =
        new GoogleStreamingRemoteEngine(config.url_request_context_getter);
  }

  google_remote_engine->SetConfig(remote_engine_config);

  // The legacy api cannot use continuous mode.
  DCHECK(!config.is_legacy_api || !config.continuous);

  session.recognizer = new SpeechRecognizer(
      this,
      session_id,
      !config.continuous,
      google_remote_engine);
  return session_id;
}

void SpeechRecognitionManagerImpl::StartSession(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  // If there is another active session, abort that.
  if (primary_session_id_ != kSessionIDInvalid &&
      primary_session_id_ != session_id) {
    AbortSession(primary_session_id_);
  }

  primary_session_id_ = session_id;

  if (delegate_.get()) {
    delegate_->CheckRecognitionIsAllowed(
        session_id,
        base::Bind(&SpeechRecognitionManagerImpl::RecognitionAllowedCallback,
                   weak_factory_.GetWeakPtr(),
                   session_id));
  }
}

void SpeechRecognitionManagerImpl::RecognitionAllowedCallback(int session_id,
                                                              bool ask_user,
                                                              bool is_allowed) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

#if defined(OS_IOS)
  // On iOS, voice search can only be initiated by clear user action and thus
  // it is always allowed.
  DCHECK(!ask_user && is_allowed);
#else
  if (ask_user) {
    SessionsTable::iterator iter = sessions_.find(session_id);
    DCHECK(iter != sessions_.end());
    SpeechRecognitionSessionContext& context = iter->second.context;
    context.label =
        BrowserMainLoop::GetMediaStreamManager()->MakeMediaAccessRequest(
            context.render_process_id,
            context.render_view_id,
            StreamOptions(MEDIA_DEVICE_AUDIO_CAPTURE, MEDIA_NO_SERVICE),
            GURL(context.context_name),
            base::Bind(
                &SpeechRecognitionManagerImpl::MediaRequestPermissionCallback,
                weak_factory_.GetWeakPtr()));

    return;
  }
#endif  // defined(OS_IOS)

  if (is_allowed) {
    MessageLoop::current()->PostTask(FROM_HERE,
        base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
                   weak_factory_.GetWeakPtr(), session_id, EVENT_START));
  } else {
    OnRecognitionError(session_id, SpeechRecognitionError(
        SPEECH_RECOGNITION_ERROR_NOT_ALLOWED));
    MessageLoop::current()->PostTask(FROM_HERE,
        base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
                   weak_factory_.GetWeakPtr(), session_id, EVENT_ABORT));
  }
}

void SpeechRecognitionManagerImpl::MediaRequestPermissionCallback(
    const std::string& label, const MediaStreamDevices& devices) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  for (SessionsTable::iterator iter = sessions_.begin();
       iter != sessions_.end(); ++iter) {
    if (iter->second.context.label == label) {
      bool is_allowed = false;
      if (!devices.empty()) {
        // Copy the approved devices array to the context for UI indication.
        iter->second.context.devices = devices;
        is_allowed = true;
      }

      // Clear the label to indicate the request has been done.
      iter->second.context.label.clear();

      // Notify the recognition about the request result.
      RecognitionAllowedCallback(iter->first, false, is_allowed);
      break;
    }
  }
}

void SpeechRecognitionManagerImpl::AbortSession(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

#if !defined(OS_IOS)
  const SpeechRecognitionSessionContext& context =
      GetSessionContext(session_id);
  if (!context.label.empty())
    BrowserMainLoop::GetMediaStreamManager()->CancelRequest(context.label);
#endif  // !defined(OS_IOS)

  MessageLoop::current()->PostTask(FROM_HERE,
      base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
                 weak_factory_.GetWeakPtr(), session_id, EVENT_ABORT));
}

void SpeechRecognitionManagerImpl::StopAudioCaptureForSession(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

#if !defined(OS_IOS)
  const SpeechRecognitionSessionContext& context =
      GetSessionContext(session_id);
  if (!context.label.empty())
    BrowserMainLoop::GetMediaStreamManager()->CancelRequest(context.label);
#endif  // !defined(OS_IOS)

  MessageLoop::current()->PostTask(FROM_HERE,
      base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
                 weak_factory_.GetWeakPtr(), session_id, EVENT_STOP_CAPTURE));
}

// Here begins the SpeechRecognitionEventListener interface implementation,
// which will simply relay the events to the proper listener registered for the
// particular session (most likely InputTagSpeechDispatcherHost) and to the
// catch-all listener provided by the delegate (if any).

void SpeechRecognitionManagerImpl::OnRecognitionStart(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

#if !defined(OS_IOS)
  const SpeechRecognitionSessionContext& context =
      GetSessionContext(session_id);
  if (!context.devices.empty()) {
    // Notify the UI the devices are being used.
    BrowserMainLoop::GetMediaStreamManager()->NotifyUIDevicesOpened(
        context.render_process_id, context.render_view_id, context.devices);
  }
#endif  // !defined(OS_IOS)

  DCHECK_EQ(primary_session_id_, session_id);
  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnRecognitionStart(session_id);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnRecognitionStart(session_id);
}

void SpeechRecognitionManagerImpl::OnAudioStart(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  DCHECK_EQ(primary_session_id_, session_id);
  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnAudioStart(session_id);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnAudioStart(session_id);
}

void SpeechRecognitionManagerImpl::OnEnvironmentEstimationComplete(
    int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  DCHECK_EQ(primary_session_id_, session_id);
  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnEnvironmentEstimationComplete(session_id);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnEnvironmentEstimationComplete(session_id);
}

void SpeechRecognitionManagerImpl::OnSoundStart(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  DCHECK_EQ(primary_session_id_, session_id);
  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnSoundStart(session_id);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnSoundStart(session_id);
}

void SpeechRecognitionManagerImpl::OnSoundEnd(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnSoundEnd(session_id);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnSoundEnd(session_id);
}

void SpeechRecognitionManagerImpl::OnAudioEnd(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnAudioEnd(session_id);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnAudioEnd(session_id);
  MessageLoop::current()->PostTask(FROM_HERE,
      base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
                 weak_factory_.GetWeakPtr(), session_id, EVENT_AUDIO_ENDED));
}

void SpeechRecognitionManagerImpl::OnRecognitionResults(
    int session_id, const SpeechRecognitionResults& results) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnRecognitionResults(session_id, results);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnRecognitionResults(session_id, results);
}

void SpeechRecognitionManagerImpl::OnRecognitionError(
    int session_id, const SpeechRecognitionError& error) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnRecognitionError(session_id, error);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnRecognitionError(session_id, error);
}

void SpeechRecognitionManagerImpl::OnAudioLevelsChange(
    int session_id, float volume, float noise_volume) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;

  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnAudioLevelsChange(session_id, volume, noise_volume);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnAudioLevelsChange(session_id, volume, noise_volume);
}

void SpeechRecognitionManagerImpl::OnRecognitionEnd(int session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!SessionExists(session_id))
    return;
#if !defined(OS_IOS)
  const SpeechRecognitionSessionContext& context =
        GetSessionContext(session_id);
  if (!context.devices.empty()) {
    // Notify the UI the devices has been closed.
     BrowserMainLoop::GetMediaStreamManager()->NotifyUIDevicesClosed(
         context.render_process_id, context.render_view_id, context.devices);
  }
#endif  // !defined(OS_IOS)

  if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
    delegate_listener->OnRecognitionEnd(session_id);
  if (SpeechRecognitionEventListener* listener = GetListener(session_id))
    listener->OnRecognitionEnd(session_id);
  MessageLoop::current()->PostTask(FROM_HERE,
      base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
                 weak_factory_.GetWeakPtr(),
                 session_id,
                 EVENT_RECOGNITION_ENDED));
}

int SpeechRecognitionManagerImpl::GetSession(
    int render_process_id, int render_view_id, int request_id) const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  SessionsTable::const_iterator iter;
  for(iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
    const int session_id = iter->first;
    const SpeechRecognitionSessionContext& context = iter->second.context;
    if (context.render_process_id == render_process_id &&
        context.render_view_id == render_view_id &&
        context.request_id == request_id) {
      return session_id;
    }
  }
  return kSessionIDInvalid;
}

SpeechRecognitionSessionContext
SpeechRecognitionManagerImpl::GetSessionContext(int session_id) const {
  return GetSession(session_id).context;
}

void SpeechRecognitionManagerImpl::AbortAllSessionsForListener(
    SpeechRecognitionEventListener* listener) {
  // This method gracefully destroys sessions for the listener. However, since
  // the listener itself is likely to be destroyed after this call, we avoid
  // dispatching further events to it, marking the |listener_is_active| flag.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  for (SessionsTable::iterator it = sessions_.begin(); it != sessions_.end();
       ++it) {
    Session& session = it->second;
    if (session.config.event_listener == listener) {
      AbortSession(session.id);
      session.listener_is_active = false;
    }
  }
}

void SpeechRecognitionManagerImpl::AbortAllSessionsForRenderView(
    int render_process_id,
    int render_view_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  for (SessionsTable::iterator it = sessions_.begin(); it != sessions_.end();
       ++it) {
    Session& session = it->second;
    if (session.context.render_process_id == render_process_id &&
        session.context.render_view_id == render_view_id) {
      AbortSession(session.id);
    }
  }
}

// -----------------------  Core FSM implementation ---------------------------
void SpeechRecognitionManagerImpl::DispatchEvent(int session_id,
                                                 FSMEvent event) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // There are some corner cases in which the session might be deleted (due to
  // an EndRecognition event) between a request (e.g. Abort) and its dispatch.
  if (!SessionExists(session_id))
    return;

  const Session& session = GetSession(session_id);
  FSMState session_state = GetSessionState(session_id);
  DCHECK_LE(session_state, SESSION_STATE_MAX_VALUE);
  DCHECK_LE(event, EVENT_MAX_VALUE);

  // Event dispatching must be sequential, otherwise it will break all the rules
  // and the assumptions of the finite state automata model.
  DCHECK(!is_dispatching_event_);
  is_dispatching_event_ = true;
  ExecuteTransitionAndGetNextState(session, session_state, event);
  is_dispatching_event_ = false;
}

// This FSM handles the evolution of each session, from the viewpoint of the
// interaction with the user (that may be either the browser end-user which
// interacts with UI bubbles, or JS developer intracting with JS methods).
// All the events received by the SpeechRecognizer instances (one for each
// session) are always routed to the SpeechRecognitionEventListener(s)
// regardless the choices taken in this FSM.
void SpeechRecognitionManagerImpl::ExecuteTransitionAndGetNextState(
    const Session& session, FSMState session_state, FSMEvent event) {
  // Note: since we're not tracking the state of the recognizer object, rather
  // we're directly retrieving it (through GetSessionState), we see its events
  // (that are AUDIO_ENDED and RECOGNITION_ENDED) after its state evolution
  // (e.g., when we receive the AUDIO_ENDED event, the recognizer has just
  // completed the transition from CAPTURING_AUDIO to WAITING_FOR_RESULT, thus
  // we perceive the AUDIO_ENDED event in WAITING_FOR_RESULT).
  // This makes the code below a bit tricky but avoids a lot of code for
  // tracking and reconstructing asynchronously the state of the recognizer.
  switch (session_state) {
    case SESSION_STATE_IDLE:
      switch (event) {
        case EVENT_START:
          return SessionStart(session);
        case EVENT_ABORT:
          return SessionAbort(session);
        case EVENT_RECOGNITION_ENDED:
          return SessionDelete(session);
        case EVENT_STOP_CAPTURE:
          return SessionStopAudioCapture(session);
        case EVENT_AUDIO_ENDED:
          return;
      }
      break;
    case SESSION_STATE_CAPTURING_AUDIO:
      switch (event) {
        case EVENT_STOP_CAPTURE:
          return SessionStopAudioCapture(session);
        case EVENT_ABORT:
          return SessionAbort(session);
        case EVENT_START:
          return;
        case EVENT_AUDIO_ENDED:
        case EVENT_RECOGNITION_ENDED:
          return NotFeasible(session, event);
      }
      break;
    case SESSION_STATE_WAITING_FOR_RESULT:
      switch (event) {
        case EVENT_ABORT:
          return SessionAbort(session);
        case EVENT_AUDIO_ENDED:
          return ResetCapturingSessionId(session);
        case EVENT_START:
        case EVENT_STOP_CAPTURE:
          return;
        case EVENT_RECOGNITION_ENDED:
          return NotFeasible(session, event);
      }
      break;
  }
  return NotFeasible(session, event);
}

SpeechRecognitionManagerImpl::FSMState
SpeechRecognitionManagerImpl::GetSessionState(int session_id) const {
  const Session& session = GetSession(session_id);
  if (!session.recognizer.get() || !session.recognizer->IsActive())
    return SESSION_STATE_IDLE;
  if (session.recognizer->IsCapturingAudio())
    return SESSION_STATE_CAPTURING_AUDIO;
  return SESSION_STATE_WAITING_FOR_RESULT;
}

// ----------- Contract for all the FSM evolution functions below -------------
//  - Are guaranteed to be executed in the IO thread;
//  - Are guaranteed to be not reentrant (themselves and each other);

void SpeechRecognitionManagerImpl::SessionStart(const Session& session) {
  DCHECK_EQ(primary_session_id_, session.id);
  session.recognizer->StartRecognition();
}

void SpeechRecognitionManagerImpl::SessionAbort(const Session& session) {
  if (primary_session_id_ == session.id)
    primary_session_id_ = kSessionIDInvalid;
  DCHECK(session.recognizer.get());
  session.recognizer->AbortRecognition();
}

void SpeechRecognitionManagerImpl::SessionStopAudioCapture(
    const Session& session) {
  DCHECK(session.recognizer.get());
  session.recognizer->StopAudioCapture();
}

void SpeechRecognitionManagerImpl::ResetCapturingSessionId(
    const Session& session) {
  DCHECK_EQ(primary_session_id_, session.id);
  primary_session_id_ = kSessionIDInvalid;
}

void SpeechRecognitionManagerImpl::SessionDelete(const Session& session) {
  DCHECK(session.recognizer == NULL || !session.recognizer->IsActive());
  if (primary_session_id_ == session.id)
    primary_session_id_ = kSessionIDInvalid;
  sessions_.erase(session.id);
}

void SpeechRecognitionManagerImpl::NotFeasible(const Session& session,
                                               FSMEvent event) {
  NOTREACHED() << "Unfeasible event " << event
               << " in state " << GetSessionState(session.id)
               << " for session " << session.id;
}

int SpeechRecognitionManagerImpl::GetNextSessionID() {
  ++last_session_id_;
  // Deal with wrapping of last_session_id_. (How civilized).
  if (last_session_id_ <= 0)
    last_session_id_ = 1;
  return last_session_id_;
}

bool SpeechRecognitionManagerImpl::SessionExists(int session_id) const {
  return sessions_.find(session_id) != sessions_.end();
}

const SpeechRecognitionManagerImpl::Session&
SpeechRecognitionManagerImpl::GetSession(int session_id) const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  SessionsTable::const_iterator iter = sessions_.find(session_id);
  DCHECK(iter != sessions_.end());
  return iter->second;
}

SpeechRecognitionEventListener* SpeechRecognitionManagerImpl::GetListener(
    int session_id) const {
  const Session& session = GetSession(session_id);
  return session.listener_is_active ? session.config.event_listener : NULL;
}

SpeechRecognitionEventListener*
SpeechRecognitionManagerImpl::GetDelegateListener() const {
  return delegate_.get() ? delegate_->GetEventListener() : NULL;
}

const SpeechRecognitionSessionConfig&
SpeechRecognitionManagerImpl::GetSessionConfig(int session_id) const {
  return GetSession(session_id).config;
}

bool SpeechRecognitionManagerImpl::HasAudioInputDevices() {
  return BrowserMainLoop::GetAudioManager()->HasAudioInputDevices();
}

bool SpeechRecognitionManagerImpl::IsCapturingAudio() {
  return BrowserMainLoop::GetAudioManager()->IsRecordingInProcess();
}

string16 SpeechRecognitionManagerImpl::GetAudioInputDeviceModel() {
  return BrowserMainLoop::GetAudioManager()->GetAudioInputDeviceModel();
}

void SpeechRecognitionManagerImpl::ShowAudioInputSettings() {
  // Since AudioManager::ShowAudioInputSettings can potentially launch external
  // processes, do that in the FILE thread to not block the calling threads.
  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
                          base::Bind(&ShowAudioInputSettingsOnFileThread));
}

SpeechRecognitionManagerImpl::Session::Session()
  : id(kSessionIDInvalid),
    listener_is_active(true) {
}

SpeechRecognitionManagerImpl::Session::~Session() {
}

}  // namespace content
