Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(767)

Unified Diff: content/browser/speech/speech_recognition_manager_impl.cc

Issue 9972011: Speech refactoring: Reimplemented SpeechRecognitionManagerImpl as a FSM. (CL1.7) (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Further Satish comments. Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698