blob: ce82d6e811656b5cb8e04736fcea87536ff89412 [file] [log] [blame]
// 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;
using content::BrowserMainLoop;
using content::BrowserThread;
using content::SpeechRecognitionEventListener;
using content::SpeechRecognitionManager;
using content::SpeechRecognitionResult;
using content::SpeechRecognitionSessionContext;
using content::SpeechRecognitionSessionConfig;
namespace content {
SpeechRecognitionManager* SpeechRecognitionManager::GetInstance() {
return speech::SpeechRecognitionManagerImpl::GetInstance();
}
} // namespace content
namespace {
speech::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
namespace speech {
class SpeechRecognitionManagerImpl::PermissionRequest
: public media_stream::MediaStreamRequester {
public:
PermissionRequest(int session_id,
const base::Callback<void(bool is_allowed)>& callback)
: session_id_(session_id),
callback_(callback),
started_(false) {
}
virtual ~PermissionRequest() {
if (started_)
Abort();
}
void Start(int render_process_id, int render_view_id, const GURL& origin) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
started_ = true;
BrowserMainLoop::GetMediaStreamManager()->GenerateStream(
this,
render_process_id,
render_view_id,
media_stream::StreamOptions(/*audio=*/true, /*video=*/false),
origin,
&label_);
}
void Abort() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
started_ = false;
BrowserMainLoop::GetMediaStreamManager()->CancelGenerateStream(label_);
}
int Session() const { return session_id_; }
// MediaStreamRequester methods.
virtual void StreamGenerated(
const std::string& label,
const media_stream::StreamDeviceInfoArray& audio_devices,
const media_stream::StreamDeviceInfoArray& video_devices) OVERRIDE {
// TODO(hans): One day it would be nice to actually use the generated stream
// but right now we only use it to request permission, and then we dump it.
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
started_ = false;
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&media_stream::MediaStreamManager::StopGeneratedStream,
base::Unretained(BrowserMainLoop::GetMediaStreamManager()),
label));
callback_.Run(true);
}
virtual void StreamGenerationFailed(const std::string& label) OVERRIDE {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
started_ = false;
callback_.Run(false);
}
// The callbacks below are ignored.
virtual void AudioDeviceFailed(const std::string& label,
int index) OVERRIDE {}
virtual void VideoDeviceFailed(const std::string& label,
int index) OVERRIDE {}
virtual void DevicesEnumerated(
const std::string& label,
const media_stream::StreamDeviceInfoArray& devices) OVERRIDE {}
virtual void DeviceOpened(
const std::string& label,
const media_stream::StreamDeviceInfo& device_info) OVERRIDE {}
private:
int session_id_;
base::Callback<void(bool is_allowed)> callback_;
std::string label_;
bool started_;
};
SpeechRecognitionManagerImpl* SpeechRecognitionManagerImpl::GetInstance() {
return g_speech_recognition_manager_impl;
}
SpeechRecognitionManagerImpl::SpeechRecognitionManagerImpl()
: primary_session_id_(kSessionIDInvalid),
last_session_id_(kSessionIDInvalid),
is_dispatching_event_(false),
delegate_(content::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.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_one_shot) {
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);
session.recognizer = new SpeechRecognizer(this,
session_id,
config.is_one_shot,
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 (ask_user) {
const SpeechRecognitionSessionContext& context =
GetSessionContext(session_id);
permission_request_.reset(new PermissionRequest(
session_id,
base::Bind(&SpeechRecognitionManagerImpl::RecognitionAllowedCallback,
weak_factory_.GetWeakPtr(),
session_id,
false)));
permission_request_->Start(context.render_process_id,
context.render_view_id,
GURL(context.context_name));
return;
}
permission_request_.reset();
if (is_allowed) {
MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
weak_factory_.GetWeakPtr(), session_id, EVENT_START));
} else {
OnRecognitionError(session_id, content::SpeechRecognitionError(
content::SPEECH_RECOGNITION_ERROR_NOT_ALLOWED));
MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
weak_factory_.GetWeakPtr(), session_id, EVENT_ABORT));
}
}
void SpeechRecognitionManagerImpl::AbortSession(int session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!SessionExists(session_id))
return;
if (permission_request_.get() &&
permission_request_->Session() == session_id) {
DCHECK(permission_request_.get());
permission_request_->Abort();
}
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 (permission_request_.get() &&
permission_request_->Session() == session_id) {
DCHECK(permission_request_.get());
permission_request_->Abort();
}
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;
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::OnRecognitionResult(
int session_id, const content::SpeechRecognitionResult& result) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!SessionExists(session_id))
return;
if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
delegate_listener->OnRecognitionResult(session_id, result);
if (SpeechRecognitionEventListener* listener = GetListener(session_id))
listener->OnRecognitionResult(session_id, result);
}
void SpeechRecognitionManagerImpl::OnRecognitionError(
int session_id, const content::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 (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 speech