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..1605e18532f80007cb4e9a7a108583c9648266e9 100644 |
--- a/content/browser/speech/speech_recognition_manager_impl.cc |
+++ b/content/browser/speech/speech_recognition_manager_impl.cc |
@@ -5,361 +5,630 @@ |
#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); |
+} |
+ |
+void SpeechRecognitionManagerImpl::OnAudioStart(int session_id) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ if (!SessionExists(session_id)) |
+ return; |
- // 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); |
+ 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 const_cast<SpeechRecognitionSessionContext&>(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)); |
+ SessionsTable::iterator it = sessions_.begin(); |
+ // AbortSession is asynchronous and the session will not be removed from the |
+ // collection while we are iterating over it. |
+ while (it != sessions_.end()) { |
+ if (it->second.event_listener == listener) |
+ AbortSession(it->first); |
+ ++it; |
} |
} |
-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); |
Satish
2012/04/23 10:25:17
why are these 2 events valid in this state? should
Primiano Tucci (use gerrit)
2012/04/23 12:52:25
STOP_CAPTURE is feasible (though it makes no sense
Satish
2012/04/24 08:51:32
Makes sense. Can you mention a shorter version of
|
+ 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: |
Satish
2012/04/23 10:25:17
As I mentioned earlier the concept of a 'detached'
Primiano Tucci (use gerrit)
2012/04/23 12:52:25
Ah sorry, I thought the earlier comment was just r
Satish
2012/04/24 08:51:32
Yes renaming DETACH to BACKGROUND would be clearer
|
+ 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_ != 0) |
+ 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 ininfluent, 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 { |
+ return sessions_.find(session_id)->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 |