Chromium Code Reviews| 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..c0f36e70ce3d3145be4b08590051e3ae9772bf35 100644 |
| --- a/content/browser/speech/speech_recognition_manager_impl.cc |
| +++ b/content/browser/speech/speech_recognition_manager_impl.cc |
| @@ -5,361 +5,636 @@ |
| #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 : |
|
jam
2012/04/24 15:56:32
the convention we have for other delegates is to n
Primiano Tucci (use gerrit)
2012/04/25 11:30:03
I thought it was more efficient and "robust" (no n
|
| + 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, send it to background. |
| + if (interactive_session_id_ != kSessionIDInvalid && |
| + interactive_session_id_ != session_id) { |
| + SendSessionToBackground(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::SendSessionToBackground(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_SET_BACKGROUND))); |
| } |
| -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_BACKGROUND || |
| + 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. |
| + // - Since this class methods are publicly exported, START, ABORT, |
| + // STOP_CAPTURE and SET_BACKGROUND events can arrive in every moment from |
| + // the outside wild wolrd, even if they make no sense. |
| + const FSMEvent event = event_args.event; |
| + switch (session.state) { |
| + case STATE_IDLE: |
| + // Session has just been created or had an error while interactive. |
| + switch (event) { |
| + case EVENT_START: |
| + return SessionStart(session, event_args); |
| + case EVENT_ABORT: |
| + case EVENT_SET_BACKGROUND: |
| + return SessionAbort(session, event_args); |
| + case EVENT_STOP_CAPTURE: |
| + case EVENT_RECOGNITION_ENDED: |
| + // In case of error, we come back in this state before receiving the |
| + // OnRecognitionEnd event, thus EVENT_RECOGNITION_ENDED is feasible. |
| + 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 SessionSetBackground(session, event_args); |
| + case EVENT_SET_BACKGROUND: |
| + return SessionAbortIfCapturingAudioOrBackground(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 sent to background). |
| + return SessionReportNoMatch(session, event_args); |
| + case EVENT_START: |
| + return DoNothing(session, event_args); |
| + } |
| + break; |
| + case STATE_BACKGROUND: |
| + 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_SET_BACKGROUND: |
| + 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_SET_BACKGROUND: |
| + 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::SessionAbortIfCapturingAudioOrBackground( |
| + 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_BACKGROUND; |
| } |
| -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::SessionSetBackground( |
| + Session& session, const FSMEventArgs& event_args) { |
| + DCHECK_EQ(interactive_session_id_, session.id); |
| + interactive_session_id_ = kSessionIDInvalid; |
| + delegate_->DoClose(session.id); |
| + return STATE_BACKGROUND; |
| } |
| -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 |