| Index: content/browser/speech/speech_recognition_manager_impl.cc
|
| diff --git a/content/browser/speech/speech_recognition_manager_impl.cc b/content/browser/speech/speech_recognition_manager_impl.cc
|
| index ed567e1996903a9ccaecc6e84df97095d1fc9163..6db0e7d55c75695dfc6b5c1981d0793d2b309367 100644
|
| --- a/content/browser/speech/speech_recognition_manager_impl.cc
|
| +++ b/content/browser/speech/speech_recognition_manager_impl.cc
|
| @@ -5,361 +5,632 @@
|
| #include "content/browser/speech/speech_recognition_manager_impl.h"
|
|
|
| #include "base/bind.h"
|
| +#include "base/memory/singleton.h"
|
| #include "content/browser/browser_main_loop.h"
|
| -#include "content/browser/renderer_host/render_view_host_impl.h"
|
| -#include "content/browser/speech/input_tag_speech_dispatcher_host.h"
|
| +#include "content/browser/speech/google_one_shot_remote_engine.h"
|
| +#include "content/browser/speech/speech_recognition_engine.h"
|
| +#include "content/browser/speech/speech_recognizer_impl.h"
|
| #include "content/public/browser/browser_thread.h"
|
| #include "content/public/browser/content_browser_client.h"
|
| -#include "content/public/browser/speech_recognizer.h"
|
| -#include "content/public/browser/render_view_host_delegate.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_preferences.h"
|
| -#include "content/public/common/view_type.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_result.h"
|
| #include "media/audio/audio_manager.h"
|
|
|
| +using base::Callback;
|
| +using base::Unretained;
|
| using content::BrowserMainLoop;
|
| using content::BrowserThread;
|
| -using content::RenderViewHostImpl;
|
| +using content::SpeechRecognitionError;
|
| +using content::SpeechRecognitionEventListener;
|
| using content::SpeechRecognitionManager;
|
| -using content::SpeechRecognitionManagerDelegate;
|
| +using content::SpeechRecognitionResult;
|
| +using content::SpeechRecognitionSessionContext;
|
| +using content::SpeechRecognitionSessionConfig;
|
| +
|
| +namespace {
|
| +
|
| +// A dummy implementation of the SpeechRecognitionManagerDelegate interface
|
| +// used when no delegate has been passed to the SpeechRecognitionManagerImpl.
|
| +class VoidRecognitionManagerDelegate :
|
| + public content::SpeechRecognitionManagerDelegate {
|
| + public:
|
| + static VoidRecognitionManagerDelegate* GetInstance() {
|
| + return Singleton<VoidRecognitionManagerDelegate>::get();
|
| + }
|
| + virtual void GetDiagnosticInformation(
|
| + bool* can_report_metrics, std::string* request_info) OVERRIDE {}
|
| + virtual bool IsRecognitionAllowed(int session_id) OVERRIDE { return false; }
|
| + virtual void ShowRecognitionRequested(int session_id) OVERRIDE {}
|
| + virtual void ShowWarmUp(int session_id) OVERRIDE {}
|
| + virtual void ShowRecognizing(int session_id) OVERRIDE {}
|
| + virtual void ShowRecording(int session_id) OVERRIDE {}
|
| + virtual void ShowInputVolume(
|
| + int session_id, float volume, float noise_volume) OVERRIDE {}
|
| + virtual void ShowError(int session_id,
|
| + const content::SpeechRecognitionError& error) OVERRIDE {}
|
| + virtual void DoClose(int session_id) OVERRIDE {}
|
| +
|
| + private:
|
| + VoidRecognitionManagerDelegate() {}
|
| + virtual ~VoidRecognitionManagerDelegate() {}
|
| + friend struct DefaultSingletonTraits<VoidRecognitionManagerDelegate>;
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +namespace content {
|
| +const int SpeechRecognitionManager::kSessionIDInvalid = 0;
|
|
|
| SpeechRecognitionManager* SpeechRecognitionManager::GetInstance() {
|
| return speech::SpeechRecognitionManagerImpl::GetInstance();
|
| }
|
| +} // namespace content
|
|
|
| namespace speech {
|
|
|
| -struct SpeechRecognitionManagerImpl::SpeechRecognitionParams {
|
| - SpeechRecognitionParams(
|
| - InputTagSpeechDispatcherHost* delegate,
|
| - int session_id,
|
| - int render_process_id,
|
| - int render_view_id,
|
| - const gfx::Rect& element_rect,
|
| - const std::string& language,
|
| - const std::string& grammar,
|
| - const std::string& origin_url,
|
| - net::URLRequestContextGetter* context_getter,
|
| - content::SpeechRecognitionPreferences* recognition_prefs)
|
| - : delegate(delegate),
|
| - session_id(session_id),
|
| - render_process_id(render_process_id),
|
| - render_view_id(render_view_id),
|
| - element_rect(element_rect),
|
| - language(language),
|
| - grammar(grammar),
|
| - origin_url(origin_url),
|
| - context_getter(context_getter),
|
| - recognition_prefs(recognition_prefs) {
|
| - }
|
| -
|
| - InputTagSpeechDispatcherHost* delegate;
|
| - int session_id;
|
| - int render_process_id;
|
| - int render_view_id;
|
| - gfx::Rect element_rect;
|
| - std::string language;
|
| - std::string grammar;
|
| - std::string origin_url;
|
| - net::URLRequestContextGetter* context_getter;
|
| - content::SpeechRecognitionPreferences* recognition_prefs;
|
| -};
|
| -
|
| SpeechRecognitionManagerImpl* SpeechRecognitionManagerImpl::GetInstance() {
|
| return Singleton<SpeechRecognitionManagerImpl>::get();
|
| }
|
|
|
| SpeechRecognitionManagerImpl::SpeechRecognitionManagerImpl()
|
| - : can_report_metrics_(false),
|
| - recording_session_id_(0) {
|
| - delegate_.reset(content::GetContentClient()->browser()->
|
| - GetSpeechRecognitionManagerDelegate());
|
| + : interactive_session_id_(kSessionIDInvalid),
|
| + last_session_id_(kSessionIDInvalid),
|
| + is_dispatching_event_(false) {
|
| + delegate_ = content::GetContentClient()->browser()->
|
| + GetSpeechRecognitionManagerDelegate();
|
| + // In lack of one being provided, instantiate a void delegate so we can avoid
|
| + // unaesthetic "if (delegate_ != NULL)" statements.
|
| + if (delegate_ == NULL)
|
| + delegate_ = VoidRecognitionManagerDelegate::GetInstance();
|
| }
|
|
|
| SpeechRecognitionManagerImpl::~SpeechRecognitionManagerImpl() {
|
| - while (requests_.begin() != requests_.end())
|
| - CancelRecognition(requests_.begin()->first);
|
| + // Recognition sessions will be aborted by the corresponding destructors.
|
| + sessions_.clear();
|
| }
|
|
|
| -bool SpeechRecognitionManagerImpl::HasAudioInputDevices() {
|
| - return BrowserMainLoop::GetAudioManager()->HasAudioInputDevices();
|
| -}
|
| +int SpeechRecognitionManagerImpl::CreateSession(
|
| + const SpeechRecognitionSessionConfig& config,
|
| + SpeechRecognitionEventListener* event_listener) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
|
|
| -bool SpeechRecognitionManagerImpl::IsCapturingAudio() {
|
| - return BrowserMainLoop::GetAudioManager()->IsRecordingInProcess();
|
| -}
|
| + const int session_id = GetNextSessionID();
|
| + DCHECK(!SessionExists(session_id));
|
| + // Set-up the new session.
|
| + Session& session = sessions_[session_id];
|
| + session.id = session_id;
|
| + session.event_listener = event_listener;
|
| + session.context = config.initial_context;
|
| +
|
| + // TODO(primiano) Is this check enough just on creation or shall we move/copy
|
| + // it on SessionStart in order to repeat the check every time?
|
| + if (!delegate_->IsRecognitionAllowed(session_id)) {
|
| + sessions_.erase(session_id);
|
| + return kSessionIDInvalid;
|
| + }
|
|
|
| -string16 SpeechRecognitionManagerImpl::GetAudioInputDeviceModel() {
|
| - return BrowserMainLoop::GetAudioManager()->GetAudioInputDeviceModel();
|
| -}
|
| + std::string hardware_info;
|
| + bool can_report_metrics;
|
| + delegate_->GetDiagnosticInformation(&can_report_metrics, &hardware_info);
|
|
|
| -bool SpeechRecognitionManagerImpl::HasPendingRequest(int session_id) const {
|
| - return requests_.find(session_id) != requests_.end();
|
| -}
|
| + GoogleOneShotRemoteEngineConfig remote_engine_config;
|
| + remote_engine_config.language = config.language;
|
| + remote_engine_config.grammar = config.grammar;
|
| + remote_engine_config.audio_sample_rate =
|
| + SpeechRecognizerImpl::kAudioSampleRate;
|
| + remote_engine_config.audio_num_bits_per_sample =
|
| + SpeechRecognizerImpl::kNumBitsPerAudioSample;
|
| + remote_engine_config.filter_profanities = config.filter_profanities;
|
| + remote_engine_config.hardware_info = hardware_info;
|
| + remote_engine_config.origin_url = can_report_metrics ? config.origin_url : "";
|
|
|
| -InputTagSpeechDispatcherHost* SpeechRecognitionManagerImpl::GetDelegate(
|
| - int session_id) const {
|
| - return requests_.find(session_id)->second.delegate;
|
| + GoogleOneShotRemoteEngine* google_remote_engine =
|
| + new GoogleOneShotRemoteEngine(config.url_request_context_getter);
|
| + google_remote_engine->SetConfig(remote_engine_config);
|
| +
|
| + session.recognizer = new SpeechRecognizerImpl(this,
|
| + session_id,
|
| + google_remote_engine);
|
| + return session_id;
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::ShowAudioInputSettings() {
|
| - // Since AudioManager::ShowAudioInputSettings can potentially launch external
|
| - // processes, do that in the FILE thread to not block the calling threads.
|
| - if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
|
| - BrowserThread::PostTask(
|
| - BrowserThread::FILE, FROM_HERE,
|
| - base::Bind(&SpeechRecognitionManagerImpl::ShowAudioInputSettings,
|
| - base::Unretained(this)));
|
| - return;
|
| +void SpeechRecognitionManagerImpl::StartSession(int session_id) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + DCHECK(SessionExists(session_id));
|
| +
|
| + // If there is another interactive session, detach prior to start the new one.
|
| + if (interactive_session_id_ != kSessionIDInvalid &&
|
| + interactive_session_id_ != session_id) {
|
| + DetachSession(interactive_session_id_);
|
| }
|
|
|
| - media::AudioManager* audio_manager = BrowserMainLoop::GetAudioManager();
|
| - DCHECK(audio_manager->CanShowAudioInputSettings());
|
| - if (audio_manager->CanShowAudioInputSettings())
|
| - audio_manager->ShowAudioInputSettings();
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent, Unretained(this),
|
| + session_id, FSMEventArgs(EVENT_START)));
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::StartRecognition(
|
| - InputTagSpeechDispatcherHost* delegate,
|
| - int session_id,
|
| - int render_process_id,
|
| - int render_view_id,
|
| - const gfx::Rect& element_rect,
|
| - const std::string& language,
|
| - const std::string& grammar,
|
| - const std::string& origin_url,
|
| - net::URLRequestContextGetter* context_getter,
|
| - content::SpeechRecognitionPreferences* recognition_prefs) {
|
| +void SpeechRecognitionManagerImpl::AbortSession(int session_id) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| - BrowserThread::PostTask(
|
| - BrowserThread::UI, FROM_HERE,
|
| - base::Bind(
|
| - &SpeechRecognitionManagerImpl::CheckRenderViewTypeAndStartRecognition,
|
| - base::Unretained(this),
|
| - SpeechRecognitionParams(
|
| - delegate, session_id, render_process_id, render_view_id,
|
| - element_rect, language, grammar, origin_url, context_getter,
|
| - recognition_prefs)));
|
| -}
|
| -
|
| -void SpeechRecognitionManagerImpl::CheckRenderViewTypeAndStartRecognition(
|
| - const SpeechRecognitionParams& params) {
|
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| -
|
| - RenderViewHostImpl* render_view_host = RenderViewHostImpl::FromID(
|
| - params.render_process_id, params.render_view_id);
|
| - if (!render_view_host || !render_view_host->GetDelegate())
|
| - return;
|
| + DCHECK(SessionExists(session_id));
|
|
|
| - // For host delegates other than VIEW_TYPE_WEB_CONTENTS we can't reliably show
|
| - // a popup, including the speech input bubble. In these cases for privacy
|
| - // reasons we don't want to start recording if the user can't be properly
|
| - // notified. An example of this is trying to show the speech input bubble
|
| - // within an extension popup: http://crbug.com/92083. In these situations the
|
| - // speech input extension API should be used instead.
|
| - if (render_view_host->GetDelegate()->GetRenderViewType() ==
|
| - content::VIEW_TYPE_WEB_CONTENTS) {
|
| - BrowserThread::PostTask(
|
| - BrowserThread::IO, FROM_HERE,
|
| - base::Bind(&SpeechRecognitionManagerImpl::ProceedStartingRecognition,
|
| - base::Unretained(this), params));
|
| - }
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent, Unretained(this),
|
| + session_id, FSMEventArgs(EVENT_ABORT)));
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::ProceedStartingRecognition(
|
| - const SpeechRecognitionParams& params) {
|
| +void SpeechRecognitionManagerImpl::StopAudioCaptureForSession(int session_id) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| - DCHECK(!HasPendingRequest(params.session_id));
|
| + DCHECK(SessionExists(session_id));
|
|
|
| - if (delegate_.get()) {
|
| - delegate_->ShowRecognitionRequested(
|
| - params.session_id, params.render_process_id, params.render_view_id,
|
| - params.element_rect);
|
| - delegate_->GetRequestInfo(&can_report_metrics_, &request_info_);
|
| - }
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent, Unretained(this),
|
| + session_id, FSMEventArgs(EVENT_STOP_CAPTURE)));
|
| +}
|
|
|
| - Request* request = &requests_[params.session_id];
|
| - request->delegate = params.delegate;
|
| - request->recognizer = content::SpeechRecognizer::Create(
|
| - this, params.session_id, params.language, params.grammar,
|
| - params.context_getter, params.recognition_prefs->FilterProfanities(),
|
| - request_info_, can_report_metrics_ ? params.origin_url : "");
|
| - request->is_active = false;
|
| +void SpeechRecognitionManagerImpl::DetachSession(int session_id) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + DCHECK(SessionExists(session_id));
|
|
|
| - StartRecognitionForRequest(params.session_id);
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent, Unretained(this),
|
| + session_id, FSMEventArgs(EVENT_DETACH)));
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::StartRecognitionForRequest(int session_id) {
|
| - SpeechRecognizerMap::iterator request = requests_.find(session_id);
|
| - if (request == requests_.end()) {
|
| - NOTREACHED();
|
| +// 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 intercept
|
| +// some of them to provide UI notifications.
|
| +
|
| +void SpeechRecognitionManagerImpl::OnRecognitionStart(int session_id) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| return;
|
| - }
|
|
|
| - // We should not currently be recording for the session.
|
| - CHECK(recording_session_id_ != session_id);
|
| + DCHECK_EQ(interactive_session_id_, session_id);
|
| + delegate_->ShowWarmUp(session_id);
|
| + GetListener(session_id)->OnRecognitionStart(session_id);
|
| +}
|
|
|
| - // If we are currently recording audio for another session, abort it cleanly.
|
| - if (recording_session_id_)
|
| - CancelRecognitionAndInformDelegate(recording_session_id_);
|
| - recording_session_id_ = session_id;
|
| - requests_[session_id].is_active = true;
|
| - requests_[session_id].recognizer->StartRecognition();
|
| - if (delegate_.get())
|
| - delegate_->ShowWarmUp(session_id);
|
| +void SpeechRecognitionManagerImpl::OnAudioStart(int session_id) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| + return;
|
| +
|
| + DCHECK_EQ(interactive_session_id_, session_id);
|
| + delegate_->ShowRecording(session_id);
|
| + GetListener(session_id)->OnAudioStart(session_id);
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::CancelRecognitionForRequest(int session_id) {
|
| - // Ignore if the session id was not in our active recognizers list because the
|
| - // user might have clicked more than once, or recognition could have been
|
| - // ended due to other reasons before the user click was processed.
|
| - if (!HasPendingRequest(session_id))
|
| +void SpeechRecognitionManagerImpl::OnEnvironmentEstimationComplete(
|
| + int session_id) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| return;
|
|
|
| - CancelRecognitionAndInformDelegate(session_id);
|
| + DCHECK_EQ(interactive_session_id_, session_id);
|
| + GetListener(session_id)->OnEnvironmentEstimationComplete(session_id);
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::FocusLostForRequest(int session_id) {
|
| - // See above comment.
|
| - if (!HasPendingRequest(session_id))
|
| +void SpeechRecognitionManagerImpl::OnSoundStart(int session_id) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| return;
|
|
|
| - // If this is an ongoing recording or if we were displaying an error message
|
| - // to the user, abort it since user has switched focus. Otherwise
|
| - // recognition has started and keep that going so user can start speaking to
|
| - // another element while this gets the results in parallel.
|
| - if (recording_session_id_ == session_id || !requests_[session_id].is_active)
|
| - CancelRecognitionAndInformDelegate(session_id);
|
| -}
|
| -
|
| -void SpeechRecognitionManagerImpl::CancelRecognition(int session_id) {
|
| - DCHECK(HasPendingRequest(session_id));
|
| - if (requests_[session_id].is_active)
|
| - requests_[session_id].recognizer->AbortRecognition();
|
| - requests_.erase(session_id);
|
| - if (recording_session_id_ == session_id)
|
| - recording_session_id_ = 0;
|
| - if (delegate_.get())
|
| - delegate_->DoClose(session_id);
|
| -}
|
| -
|
| -void SpeechRecognitionManagerImpl::CancelAllRequestsWithDelegate(
|
| - InputTagSpeechDispatcherHost* delegate) {
|
| - SpeechRecognizerMap::iterator it = requests_.begin();
|
| - while (it != requests_.end()) {
|
| - if (it->second.delegate == delegate) {
|
| - CancelRecognition(it->first);
|
| - // This map will have very few elements so it is simpler to restart.
|
| - it = requests_.begin();
|
| - } else {
|
| - ++it;
|
| - }
|
| - }
|
| + DCHECK_EQ(interactive_session_id_, session_id);
|
| + GetListener(session_id)->OnSoundStart(session_id);
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::StopRecording(int session_id) {
|
| - // No pending requests on extension popups.
|
| - if (!HasPendingRequest(session_id))
|
| +void SpeechRecognitionManagerImpl::OnSoundEnd(int session_id) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| return;
|
|
|
| - requests_[session_id].recognizer->StopAudioCapture();
|
| + GetListener(session_id)->OnSoundEnd(session_id);
|
| }
|
|
|
| -// -------- SpeechRecognitionEventListener interface implementation. ---------
|
| +void SpeechRecognitionManagerImpl::OnAudioEnd(int session_id) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| + return;
|
| +
|
| + // OnAudioEnd can also be raised after an abort request, when the session is
|
| + // not interactive anymore.
|
| + if (interactive_session_id_ == session_id)
|
| + delegate_->ShowRecognizing(session_id);
|
| +
|
| + GetListener(session_id)->OnAudioEnd(session_id);
|
| +}
|
|
|
| void SpeechRecognitionManagerImpl::OnRecognitionResult(
|
| int session_id, const content::SpeechRecognitionResult& result) {
|
| - DCHECK(HasPendingRequest(session_id));
|
| - GetDelegate(session_id)->SetRecognitionResult(session_id, result);
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| + return;
|
| +
|
| + GetListener(session_id)->OnRecognitionResult(session_id, result);
|
| + FSMEventArgs event_args(EVENT_RECOGNITION_RESULT);
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent, Unretained(this),
|
| + session_id, event_args));
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::OnAudioEnd(int session_id) {
|
| - if (recording_session_id_ != session_id)
|
| +void SpeechRecognitionManagerImpl::OnRecognitionError(
|
| + int session_id, const content::SpeechRecognitionError& error) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| return;
|
| - DCHECK_EQ(recording_session_id_, session_id);
|
| - DCHECK(HasPendingRequest(session_id));
|
| - if (!requests_[session_id].is_active)
|
| +
|
| + GetListener(session_id)->OnRecognitionError(session_id, error);
|
| + FSMEventArgs event_args(EVENT_RECOGNITION_ERROR);
|
| + event_args.speech_error = error;
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent, Unretained(this),
|
| + session_id, event_args));
|
| +}
|
| +
|
| +void SpeechRecognitionManagerImpl::OnAudioLevelsChange(
|
| + int session_id, float volume, float noise_volume) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| return;
|
| - recording_session_id_ = 0;
|
| - GetDelegate(session_id)->DidCompleteRecording(session_id);
|
| - if (delegate_.get())
|
| - delegate_->ShowRecognizing(session_id);
|
| +
|
| + delegate_->ShowInputVolume(session_id, volume, noise_volume);
|
| + GetListener(session_id)->OnAudioLevelsChange(session_id, volume,
|
| + noise_volume);
|
| }
|
|
|
| void SpeechRecognitionManagerImpl::OnRecognitionEnd(int session_id) {
|
| - if (!HasPendingRequest(session_id) || !requests_[session_id].is_active)
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| return;
|
| - GetDelegate(session_id)->DidCompleteRecognition(session_id);
|
| - requests_.erase(session_id);
|
| - if (delegate_.get())
|
| - delegate_->DoClose(session_id);
|
| +
|
| + GetListener(session_id)->OnRecognitionEnd(session_id);
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent, Unretained(this),
|
| + session_id, FSMEventArgs(EVENT_RECOGNITION_ENDED)));
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::OnSoundStart(int session_id) {
|
| +// TODO(primiano) After CL2: if we see that both InputTagDispatcherHost and
|
| +// SpeechRecognitionDispatcherHost do the same lookup operations, implement the
|
| +// lookup method directly here.
|
| +int SpeechRecognitionManagerImpl::LookupSessionByContext(
|
| + Callback<bool(const SpeechRecognitionSessionContext&)> matcher) const {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + SessionsTable::const_iterator iter;
|
| + // Note: the callback (matcher) must NEVER perform non-const calls on us.
|
| + for(iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
|
| + const int session_id = iter->first;
|
| + const Session& session = iter->second;
|
| + bool matches = matcher.Run(session.context);
|
| + if (matches)
|
| + return session_id;
|
| + }
|
| + return kSessionIDInvalid;
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::OnSoundEnd(int session_id) {
|
| +SpeechRecognitionSessionContext
|
| +SpeechRecognitionManagerImpl::GetSessionContext(int session_id) const {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + SessionsTable::const_iterator iter = sessions_.find(session_id);
|
| + DCHECK(iter != sessions_.end());
|
| + return iter->second.context;
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::OnRecognitionError(
|
| - int session_id, const content::SpeechRecognitionError& error) {
|
| - DCHECK(HasPendingRequest(session_id));
|
| - if (session_id == recording_session_id_)
|
| - recording_session_id_ = 0;
|
| - requests_[session_id].is_active = false;
|
| - if (delegate_.get()) {
|
| - if (error.code == content::SPEECH_RECOGNITION_ERROR_AUDIO &&
|
| - error.details == content::SPEECH_AUDIO_ERROR_DETAILS_NO_MIC) {
|
| - delegate_->ShowMicError(session_id,
|
| - SpeechRecognitionManagerDelegate::MIC_ERROR_NO_DEVICE_AVAILABLE);
|
| - } else if (error.code == content::SPEECH_RECOGNITION_ERROR_AUDIO &&
|
| - error.details == content::SPEECH_AUDIO_ERROR_DETAILS_IN_USE) {
|
| - delegate_->ShowMicError(session_id,
|
| - SpeechRecognitionManagerDelegate::MIC_ERROR_DEVICE_IN_USE);
|
| - } else {
|
| - delegate_->ShowRecognizerError(session_id, error.code);
|
| - }
|
| +void SpeechRecognitionManagerImpl::AbortAllSessionsForListener(
|
| + SpeechRecognitionEventListener* listener) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| +
|
| + // AbortSession is asynchronous and the session will not be removed from the
|
| + // collection while we are iterating over it.
|
| + for (SessionsTable::iterator it = sessions_.begin(); it != sessions_.end();
|
| + ++it) {
|
| + if (it->second.event_listener == listener)
|
| + AbortSession(it->first);
|
| }
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::OnAudioStart(int session_id) {
|
| - DCHECK(HasPendingRequest(session_id));
|
| - DCHECK_EQ(recording_session_id_, session_id);
|
| - if (delegate_.get())
|
| - delegate_->ShowRecording(session_id);
|
| +// ----------------------- Core FSM implementation ---------------------------
|
| +void SpeechRecognitionManagerImpl::DispatchEvent(int session_id,
|
| + FSMEventArgs event_args) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (!SessionExists(session_id))
|
| + return;
|
| +
|
| + Session& session = sessions_[session_id];
|
| + DCHECK_LE(session.state, STATE_MAX_VALUE);
|
| + DCHECK_LE(event_args.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;
|
| +
|
| + // Pedantic preconditions consistency checks.
|
| + if (session.state == STATE_INTERACTIVE)
|
| + DCHECK_EQ(interactive_session_id_, session_id);
|
| +
|
| + if (session.state == STATE_DETACHED ||
|
| + session.state == STATE_WAITING_FOR_DELETION) {
|
| + DCHECK_NE(interactive_session_id_, session_id);
|
| + }
|
| +
|
| + session.state = ExecuteTransitionAndGetNextState(session, event_args);
|
| +
|
| + 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 SpeechRecognizerImpl instances (one for each
|
| +// session) are always routed to the SpeechRecognitionEventListener(s)
|
| +// regardless the choices taken in this FSM.
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::ExecuteTransitionAndGetNextState(
|
| + Session& session, const FSMEventArgs& event_args) {
|
| + // Some notes for the code below:
|
| + // - A session can be deleted only if it is not active, thus only if it ended
|
| + // spontaneously or we issued a prior SessionAbort. In these cases, we must
|
| + // wait for a RECOGNITION_ENDED event (which is guaranteed to come always at
|
| + // last by the SpeechRecognizer) in order to free resources gracefully.
|
| + // - Use SessionDelete only when absolutely sure that the recognizer is not
|
| + // active. Prefer SessionAbort, which will do it gracefully, otherwise.
|
| + const FSMEvent event = event_args.event;
|
| + switch (session.state) {
|
| + case STATE_IDLE:
|
| + // Session has just been created or had an error while interactive (thus,
|
| + // it is still interactive).
|
| + switch (event) {
|
| + case EVENT_START:
|
| + return SessionStart(session, event_args);
|
| + case EVENT_ABORT:
|
| + case EVENT_DETACH:
|
| + return SessionAbort(session, event_args);
|
| + case EVENT_STOP_CAPTURE:
|
| + case EVENT_RECOGNITION_ENDED:
|
| + return DoNothing(session, event_args);
|
| + case EVENT_RECOGNITION_RESULT:
|
| + case EVENT_RECOGNITION_ERROR:
|
| + return NotFeasible(session, event_args);
|
| + }
|
| + break;
|
| + case STATE_INTERACTIVE:
|
| + // The recognizer can be either capturing audio or waiting for a result.
|
| + switch (event) {
|
| + case EVENT_RECOGNITION_RESULT:
|
| + // TODO(primiano) Valid only in single shot mode. Review in next CLs.
|
| + return SessionDetach(session, event_args);
|
| + case EVENT_DETACH:
|
| + return SessionAbortIfCapturingAudioOrDetach(session, event_args);
|
| + case EVENT_STOP_CAPTURE:
|
| + return SessionStopAudioCapture(session, event_args);
|
| + case EVENT_ABORT:
|
| + return SessionAbort(session, event_args);
|
| + case EVENT_RECOGNITION_ERROR:
|
| + return SessionReportError(session, event_args);
|
| + case EVENT_RECOGNITION_ENDED:
|
| + // If we're still interactive it means that no result was received
|
| + // in the meanwhile (otherwise we'd have been detached).
|
| + return SessionReportNoMatch(session, event_args);
|
| + case EVENT_START:
|
| + return DoNothing(session, event_args);
|
| + }
|
| + break;
|
| + case STATE_DETACHED:
|
| + switch (event) {
|
| + case EVENT_ABORT:
|
| + return SessionAbort(session, event_args);
|
| + case EVENT_RECOGNITION_ENDED:
|
| + return SessionDelete(session, event_args);
|
| + case EVENT_START:
|
| + case EVENT_STOP_CAPTURE:
|
| + case EVENT_RECOGNITION_RESULT:
|
| + case EVENT_RECOGNITION_ERROR:
|
| + return DoNothing(session, event_args);
|
| + case EVENT_DETACH:
|
| + return NotFeasible(session, event_args);
|
| + }
|
| + break;
|
| + case STATE_WAITING_FOR_DELETION:
|
| + switch (event) {
|
| + case EVENT_RECOGNITION_ENDED:
|
| + return SessionDelete(session, event_args);
|
| + case EVENT_ABORT:
|
| + case EVENT_START:
|
| + case EVENT_STOP_CAPTURE:
|
| + case EVENT_DETACH:
|
| + case EVENT_RECOGNITION_RESULT:
|
| + case EVENT_RECOGNITION_ERROR:
|
| + return DoNothing(session, event_args);
|
| + }
|
| + break;
|
| + }
|
| + return NotFeasible(session, event_args);
|
| +}
|
| +
|
| +// ----------- 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);
|
| +// - event_args members are guaranteed to be stable during the call;
|
| +
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::SessionStart(Session& session,
|
| + const FSMEventArgs& event_args) {
|
| + if (interactive_session_id_ != kSessionIDInvalid)
|
| + delegate_->DoClose(interactive_session_id_);
|
| + interactive_session_id_ = session.id;
|
| + delegate_->ShowRecognitionRequested(session.id);
|
| + session.recognizer->StartRecognition();
|
| + return STATE_INTERACTIVE;
|
| +}
|
| +
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::SessionAbort(Session& session,
|
| + const FSMEventArgs& event_args) {
|
| + if (interactive_session_id_ == session.id) {
|
| + interactive_session_id_ = kSessionIDInvalid;
|
| + delegate_->DoClose(session.id);
|
| + }
|
| +
|
| + // If abort was requested while the recognizer was inactive, delete directly.
|
| + if (session.recognizer == NULL || !session.recognizer->IsActive())
|
| + return SessionDelete(session, event_args);
|
| +
|
| + // Otherwise issue an abort and delete gracefully, waiting for a
|
| + // RECOGNITION_ENDED event first.
|
| + session.recognizer->AbortRecognition();
|
| + return STATE_WAITING_FOR_DELETION;
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::OnRecognitionStart(int session_id) {
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::SessionStopAudioCapture(
|
| + Session& session, const FSMEventArgs& event_args) {
|
| + DCHECK(session.recognizer != NULL);
|
| + DCHECK(session.recognizer->IsActive());
|
| + if (session.recognizer->IsCapturingAudio())
|
| + session.recognizer->StopAudioCapture();
|
| + return STATE_INTERACTIVE;
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::OnEnvironmentEstimationComplete(
|
| - int session_id) {
|
| - DCHECK(HasPendingRequest(session_id));
|
| - DCHECK_EQ(recording_session_id_, session_id);
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::SessionAbortIfCapturingAudioOrDetach(
|
| + Session& session, const FSMEventArgs& event_args) {
|
| + DCHECK_EQ(interactive_session_id_, session.id);
|
| +
|
| + DCHECK(session.recognizer != NULL);
|
| + DCHECK(session.recognizer->IsActive());
|
| + if (session.recognizer->IsCapturingAudio())
|
| + return SessionAbort(session, event_args);
|
| +
|
| + interactive_session_id_ = kSessionIDInvalid;
|
| + delegate_->DoClose(session.id);
|
| + return STATE_DETACHED;
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::OnAudioLevelsChange(
|
| - int session_id, float volume, float noise_volume) {
|
| - DCHECK(HasPendingRequest(session_id));
|
| - DCHECK_EQ(recording_session_id_, session_id);
|
| - if (delegate_.get())
|
| - delegate_->ShowInputVolume(session_id, volume, noise_volume);
|
| +
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::SessionDetach(Session& session,
|
| + const FSMEventArgs& event_args) {
|
| + DCHECK_EQ(interactive_session_id_, session.id);
|
| + interactive_session_id_ = kSessionIDInvalid;
|
| + delegate_->DoClose(session.id);
|
| + return STATE_DETACHED;
|
| }
|
|
|
| -void SpeechRecognitionManagerImpl::CancelRecognitionAndInformDelegate(
|
| - int session_id) {
|
| - InputTagSpeechDispatcherHost* cur_delegate = GetDelegate(session_id);
|
| - CancelRecognition(session_id);
|
| - cur_delegate->DidCompleteRecording(session_id);
|
| - cur_delegate->DidCompleteRecognition(session_id);
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::SessionReportError(
|
| + Session& session, const FSMEventArgs& event_args) {
|
| + DCHECK_EQ(interactive_session_id_, session.id);
|
| + delegate_->ShowError(session.id, event_args.speech_error);
|
| + return STATE_IDLE;
|
| +}
|
| +
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::SessionReportNoMatch(
|
| + Session& session, const FSMEventArgs& event_args) {
|
| + DCHECK_EQ(interactive_session_id_, session.id);
|
| + delegate_->ShowError(
|
| + session.id,
|
| + SpeechRecognitionError(content::SPEECH_RECOGNITION_ERROR_NO_MATCH));
|
| + return STATE_IDLE;
|
| +}
|
| +
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::SessionDelete(Session& session,
|
| + const FSMEventArgs& event_args) {
|
| + DCHECK(session.recognizer == NULL || !session.recognizer->IsActive());
|
| + if (interactive_session_id_ == session.id) {
|
| + interactive_session_id_ = kSessionIDInvalid;
|
| + delegate_->DoClose(session.id);
|
| + }
|
| + sessions_.erase(session.id);
|
| + // Next state is irrelevant, the session will be deleted afterwards.
|
| + return STATE_WAITING_FOR_DELETION;
|
| +}
|
| +
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::DoNothing(Session& session,
|
| + const FSMEventArgs& event_args) {
|
| + return session.state;
|
| +}
|
| +
|
| +SpeechRecognitionManagerImpl::FSMState
|
| +SpeechRecognitionManagerImpl::NotFeasible(Session& session,
|
| + const FSMEventArgs& event_args) {
|
| + NOTREACHED() << "Unfeasible event " << event_args.event
|
| + << " in state " << session.state
|
| + << " for session " << session.id;
|
| + return session.state;
|
| +}
|
| +
|
| +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();
|
| +}
|
| +
|
| +SpeechRecognitionEventListener* SpeechRecognitionManagerImpl::GetListener(
|
| + int session_id) const {
|
| + SessionsTable::const_iterator iter = sessions_.find(session_id);
|
| + DCHECK(iter != sessions_.end());
|
| + return iter->second.event_listener;
|
| +}
|
| +
|
| +
|
| +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.
|
| + if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
|
| + BrowserThread::PostTask(
|
| + BrowserThread::FILE, FROM_HERE,
|
| + base::Bind(&SpeechRecognitionManagerImpl::ShowAudioInputSettings,
|
| + base::Unretained(this)));
|
| + return;
|
| + }
|
| +
|
| + media::AudioManager* audio_manager = BrowserMainLoop::GetAudioManager();
|
| + DCHECK(audio_manager->CanShowAudioInputSettings());
|
| + if (audio_manager->CanShowAudioInputSettings())
|
| + audio_manager->ShowAudioInputSettings();
|
| +}
|
| +
|
| +SpeechRecognitionManagerImpl::FSMEventArgs::FSMEventArgs(FSMEvent event_value)
|
| + : event(event_value),
|
| + speech_error(content::SPEECH_RECOGNITION_ERROR_NONE) {
|
| +}
|
| +
|
| +SpeechRecognitionManagerImpl::FSMEventArgs::~FSMEventArgs() {
|
| }
|
|
|
| -SpeechRecognitionManagerImpl::Request::Request()
|
| - : is_active(false) {
|
| +SpeechRecognitionManagerImpl::Session::Session()
|
| + : id(kSessionIDInvalid),
|
| + event_listener(NULL),
|
| + state(STATE_IDLE) {
|
| }
|
|
|
| -SpeechRecognitionManagerImpl::Request::~Request() {
|
| +SpeechRecognitionManagerImpl::Session::~Session() {
|
| }
|
|
|
| } // namespace speech
|
|
|