diff options
author | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-24 10:58:24 +0000 |
---|---|---|
committer | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-24 10:58:24 +0000 |
commit | d305111d5d25b32277e9b844832d19e08321db80 (patch) | |
tree | 62794788d05700882f2c4b6133df6bf8460a18fa /chrome/browser/speech | |
parent | 909fd560e81d4df154acfff63dd276297842b3b8 (diff) | |
download | chromium_src-d305111d5d25b32277e9b844832d19e08321db80.zip chromium_src-d305111d5d25b32277e9b844832d19e08321db80.tar.gz chromium_src-d305111d5d25b32277e9b844832d19e08321db80.tar.bz2 |
SpeechInputExtensionManager now interface (exclusively) with SpeechRecognitionManagerDelegate (Speech CL1.11)
This is a re-submit of r138498 which was reverted in
r138568.
Fixes introduced by this CL:
- Added a check on speech_input_extensions_manager shutdown to assert that the speech_recognition_manager has not been unexpectedly destroyed (happens in some tests).
- Added WeakPtr to speech_recognition_manager_impl so that it can be destroyed ungracefully without crashing.
Original CL description:
- The tray icon and balloon handing has been moved from speech_input_extensions_manager to chrome_speech_recognition_manager_delegate, since that code (tray icon) will be used also for continuous recognition.
- Removed the SpeechRecognizer interface from /content/public (thus de-virtualized and refcounted the SpeechRecognizerImpl)
BUG=116954,129408
TEST=browser_tests on Linux ChromiumOS (dbg)
Review URL: https://chromiumcodereview.appspot.com/10440011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@138765 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/speech')
5 files changed, 265 insertions, 142 deletions
diff --git a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc index 6b0162c..b771ac7 100644 --- a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc +++ b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc @@ -13,6 +13,7 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/speech/speech_recognition_tray_icon_controller.h" #include "chrome/browser/tab_contents/tab_util.h" #include "chrome/browser/view_type_utils.h" #include "chrome/common/pref_names.h" @@ -36,10 +37,20 @@ using content::BrowserThread; using content::SpeechRecognitionManager; using content::WebContents; +using content::SpeechRecognitionSessionContext; namespace { const int kNoActiveBubble = content::SpeechRecognitionManager::kSessionIDInvalid; + +bool RequiresBubble(int session_id) { + return SpeechRecognitionManager::GetInstance()-> + GetSessionContext(session_id).requested_by_page_element; +} + +bool RequiresTrayIcon(int session_id) { + return !RequiresBubble(session_id); +} } // namespace namespace speech { @@ -108,13 +119,17 @@ class ChromeSpeechRecognitionManagerDelegate::OptionalRequestInfo }; ChromeSpeechRecognitionManagerDelegate::ChromeSpeechRecognitionManagerDelegate() - : bubble_controller_(new SpeechRecognitionBubbleController( - ALLOW_THIS_IN_INITIALIZER_LIST(this))), - active_bubble_session_id_(kNoActiveBubble) { + : active_bubble_session_id_(kNoActiveBubble) { } ChromeSpeechRecognitionManagerDelegate:: ~ChromeSpeechRecognitionManagerDelegate() { + if (tray_icon_controller_.get()) + tray_icon_controller_->Hide(); + if (active_bubble_session_id_ != kNoActiveBubble) { + DCHECK(bubble_controller_.get()); + bubble_controller_->CloseBubble(active_bubble_session_id_); + } } void ChromeSpeechRecognitionManagerDelegate::InfoBubbleButtonClicked( @@ -122,8 +137,11 @@ void ChromeSpeechRecognitionManagerDelegate::InfoBubbleButtonClicked( DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK_EQ(active_bubble_session_id_, session_id); + // Note, the session might have been destroyed, therefore avoid calls to the + // manager which imply its existance (e.g., GetSessionContext()). + if (button == SpeechRecognitionBubble::BUTTON_CANCEL) { - bubble_controller_->CloseBubble(session_id); + GetBubbleController()->CloseBubble(session_id); last_session_config_.reset(); active_bubble_session_id_ = kNoActiveBubble; @@ -131,7 +149,7 @@ void ChromeSpeechRecognitionManagerDelegate::InfoBubbleButtonClicked( // the manager's public methods are reliable and will handle it properly. SpeechRecognitionManager::GetInstance()->AbortSession(session_id); } else if (button == SpeechRecognitionBubble::BUTTON_TRY_AGAIN) { - bubble_controller_->CloseBubble(session_id); + GetBubbleController()->CloseBubble(session_id); active_bubble_session_id_ = kNoActiveBubble; RestartLastSession(); } @@ -142,7 +160,10 @@ void ChromeSpeechRecognitionManagerDelegate::InfoBubbleFocusChanged( DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK_EQ(active_bubble_session_id_, session_id); - bubble_controller_->CloseBubble(session_id); + // Note, the session might have been destroyed, therefore avoid calls to the + // manager which imply its existance (e.g., GetSessionContext()). + + GetBubbleController()->CloseBubble(session_id); last_session_config_.reset(); active_bubble_session_id_ = kNoActiveBubble; @@ -164,25 +185,36 @@ void ChromeSpeechRecognitionManagerDelegate::RestartLastSession() { void ChromeSpeechRecognitionManagerDelegate::OnRecognitionStart( int session_id) { - // Copy the configuration of the session (for the "try again" button). - last_session_config_.reset(new content::SpeechRecognitionSessionConfig( - SpeechRecognitionManager::GetInstance()->GetSessionConfig(session_id))); - - // Create and show the bubble. - DCHECK_EQ(active_bubble_session_id_, kNoActiveBubble); - active_bubble_session_id_ = session_id; const content::SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id); - bubble_controller_->CreateBubble(session_id, - context.render_process_id, - context.render_view_id, - context.element_rect); - // TODO(primiano) Why not create directly the bubble in warmup mode? - bubble_controller_->SetBubbleWarmUpMode(session_id); + + if (RequiresBubble(session_id)) { + // Copy the configuration of the session (for the "try again" button). + last_session_config_.reset(new content::SpeechRecognitionSessionConfig( + SpeechRecognitionManager::GetInstance()->GetSessionConfig(session_id))); + + // Create and show the bubble. + DCHECK_EQ(active_bubble_session_id_, kNoActiveBubble); + active_bubble_session_id_ = session_id; + GetBubbleController()->CreateBubble(session_id, + context.render_process_id, + context.render_view_id, + context.element_rect); + + // TODO(primiano) Why not creating directly the bubble in warmup mode? + GetBubbleController()->SetBubbleWarmUpMode(session_id); + } } void ChromeSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) { - bubble_controller_->SetBubbleRecordingMode(session_id); + if (RequiresBubble(session_id)) { + GetBubbleController()->SetBubbleRecordingMode(session_id); + } else if (RequiresTrayIcon(session_id)) { + const content::SpeechRecognitionSessionContext& context = + SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id); + GetTrayIconController()->Show(context.context_name, + context.is_first_request_for_context); + } } void ChromeSpeechRecognitionManagerDelegate::OnEnvironmentEstimationComplete( @@ -198,16 +230,19 @@ void ChromeSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) { void ChromeSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) { // OnAudioEnd can be also raised after an abort, when the bubble has already // been closed. - if (active_bubble_session_id_ == session_id) - bubble_controller_->SetBubbleRecognizingMode(session_id); + if (RequiresBubble(session_id) && active_bubble_session_id_ == session_id) { + GetBubbleController()->SetBubbleRecognizingMode(session_id); + } else if (RequiresTrayIcon(session_id)) { + GetTrayIconController()->Hide(); + } } void ChromeSpeechRecognitionManagerDelegate::OnRecognitionResult( int session_id, const content::SpeechRecognitionResult& result) { // A result can be dispatched when the bubble is not visible anymore (e.g., // lost focus while waiting for a result, thus continuing in background). - if (active_bubble_session_id_ == session_id) { - bubble_controller_->CloseBubble(session_id); + if (RequiresBubble(session_id) && active_bubble_session_id_ == session_id) { + GetBubbleController()->CloseBubble(session_id); last_session_config_.reset(); active_bubble_session_id_ = kNoActiveBubble; } @@ -218,6 +253,7 @@ void ChromeSpeechRecognitionManagerDelegate::OnRecognitionError( // An error can be dispatched when the bubble is not visible anymore. if (active_bubble_session_id_ != session_id) return; + DCHECK(RequiresBubble(session_id)); int error_message_id = 0; switch (error.code) { @@ -250,17 +286,29 @@ void ChromeSpeechRecognitionManagerDelegate::OnRecognitionError( NOTREACHED() << "unknown error " << error.code; return; } - bubble_controller_->SetBubbleMessage( + GetBubbleController()->SetBubbleMessage( session_id, l10n_util::GetStringUTF16(error_message_id)); } void ChromeSpeechRecognitionManagerDelegate::OnAudioLevelsChange( int session_id, float volume, float noise_volume) { - if (active_bubble_session_id_ == session_id) - bubble_controller_->SetBubbleInputVolume(session_id, volume, noise_volume); + if (active_bubble_session_id_ == session_id) { + DCHECK(RequiresBubble(session_id)); + GetBubbleController()->SetBubbleInputVolume(session_id, + volume, noise_volume); + } else if (RequiresTrayIcon(session_id)) { + GetTrayIconController()->SetVUMeterVolume(volume); + } } void ChromeSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) { + // No need to remove the bubble here, since either one of the following events + // must have happened prior to this callback: + // - A previous OnRecognitionResult event already closed the bubble. + // - An error occurred, so the bubble is showing the error and will be closed + // when it will lose focus (by InfoBubbleFocusChanged()). + // - The bubble lost focus or the user pressed the Cancel button, thus it has + // been closed by InfoBubbleFocusChanged(), which triggered an AbortSession. } void ChromeSpeechRecognitionManagerDelegate::GetDiagnosticInformation( @@ -286,11 +334,20 @@ void ChromeSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed( int session_id, base::Callback<void(int session_id, bool is_allowed)> callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - const content::SpeechRecognitionSessionContext& context = - SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id); + // We don't need any particular check for sessions not using a bubble. In such + // cases, we just notify it to the manager (calling-back synchronously, since + // we remain in the IO thread). + if (RequiresTrayIcon(session_id)) { + callback.Run(session_id, true /* is_allowed */); + return; + } + + // Sessions using bubbles, conversely, need a check on the renderer view type. // The check must be performed in the UI thread. We defer it posting to // CheckRenderViewType, which will issue the callback on our behalf. + const content::SpeechRecognitionSessionContext& context = + SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&CheckRenderViewType, session_id, @@ -330,4 +387,19 @@ void ChromeSpeechRecognitionManagerDelegate::CheckRenderViewType( base::Bind(callback, session_id, allowed)); } +SpeechRecognitionBubbleController* +ChromeSpeechRecognitionManagerDelegate::GetBubbleController() { + if (!bubble_controller_.get()) + bubble_controller_ = new SpeechRecognitionBubbleController(this); + return bubble_controller_.get(); +} + +SpeechRecognitionTrayIconController* +ChromeSpeechRecognitionManagerDelegate::GetTrayIconController() { + if (!tray_icon_controller_.get()) + tray_icon_controller_ = new SpeechRecognitionTrayIconController(); + return tray_icon_controller_.get(); +} + + } // namespace speech diff --git a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h index e881d06..f59e451 100644 --- a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h +++ b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h @@ -13,6 +13,8 @@ #include "content/public/browser/speech_recognition_manager_delegate.h" #include "content/public/browser/speech_recognition_session_config.h" +class SpeechRecognitionTrayIconController; + namespace speech { // This is Chrome's implementation of the SpeechRecognitionManagerDelegate @@ -69,7 +71,12 @@ class ChromeSpeechRecognitionManagerDelegate // (which is copied into |last_session_config_|). Used for "try again". void RestartLastSession(); + // Lazy initializers for bubble and tray icon controller. + SpeechRecognitionBubbleController* GetBubbleController(); + SpeechRecognitionTrayIconController* GetTrayIconController(); + scoped_refptr<SpeechRecognitionBubbleController> bubble_controller_; + scoped_refptr<SpeechRecognitionTrayIconController> tray_icon_controller_; scoped_refptr<OptionalRequestInfo> optional_request_info_; scoped_ptr<content::SpeechRecognitionSessionConfig> last_session_config_; diff --git a/chrome/browser/speech/speech_input_extension_apitest.cc b/chrome/browser/speech/speech_input_extension_apitest.cc index cf5e7a9..3718a03 100644 --- a/chrome/browser/speech/speech_input_extension_apitest.cc +++ b/chrome/browser/speech/speech_input_extension_apitest.cc @@ -23,6 +23,10 @@ namespace net { class URLRequestContextGetter; } +namespace { +const int kSessionIDForTests = 0; +} + // Mock class used to test the extension speech input API. class SpeechInputExtensionApiTest : public ExtensionApiTest, public SpeechInputExtensionInterface { @@ -77,10 +81,11 @@ class SpeechInputExtensionApiTest : public ExtensionApiTest, virtual void StartRecording( content::SpeechRecognitionEventListener* listener, net::URLRequestContextGetter* context_getter, - int session_id, + const string16& extension_name, const std::string& language, const std::string& grammar, - bool filter_profanities) OVERRIDE; + bool filter_profanities, + bool show_notification) OVERRIDE; virtual void StopRecording(bool recognition_failed) OVERRIDE; @@ -107,7 +112,7 @@ class SpeechInputExtensionApiTest : public ExtensionApiTest, }; private: - void ProvideResults(int session_id); + void ProvideResults(); bool recording_devices_available_; bool recognizer_is_valid_; @@ -131,10 +136,11 @@ SpeechInputExtensionApiTest::~SpeechInputExtensionApiTest() { void SpeechInputExtensionApiTest::StartRecording( content::SpeechRecognitionEventListener* listener, net::URLRequestContextGetter* context_getter, - int session_id, + const string16& extension_name, const std::string& language, const std::string& grammar, - bool filter_profanities) { + bool filter_profanities, + bool show_notification) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); recognizer_is_valid_ = true; @@ -143,7 +149,7 @@ void SpeechInputExtensionApiTest::StartRecording( FROM_HERE, base::Bind(&SpeechInputExtensionManager::OnAudioStart, GetManager(), - session_id), + kSessionIDForTests), base::TimeDelta()); // Notify sound start in the input device. @@ -151,16 +157,14 @@ void SpeechInputExtensionApiTest::StartRecording( FROM_HERE, base::Bind(&SpeechInputExtensionManager::OnSoundStart, GetManager(), - session_id), + kSessionIDForTests), base::TimeDelta()); if (result_delay_ms_ != kDontDispatchCall) { // Dispatch the recognition results. MessageLoop::current()->PostDelayedTask( FROM_HERE, - base::Bind(&SpeechInputExtensionApiTest::ProvideResults, - this, - session_id), + base::Bind(&SpeechInputExtensionApiTest::ProvideResults, this), base::TimeDelta::FromMilliseconds(result_delay_ms_)); } } @@ -170,18 +174,19 @@ void SpeechInputExtensionApiTest::StopRecording(bool recognition_failed) { recognizer_is_valid_ = false; } -void SpeechInputExtensionApiTest::ProvideResults(int session_id) { +void SpeechInputExtensionApiTest::ProvideResults() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (next_error_ != content::SPEECH_RECOGNITION_ERROR_NONE) { GetManager()->OnRecognitionError( - session_id, content::SpeechRecognitionError(next_error_)); + kSessionIDForTests, content::SpeechRecognitionError(next_error_)); return; } - GetManager()->OnSoundEnd(session_id); - GetManager()->OnAudioEnd(session_id); - GetManager()->OnRecognitionResult(session_id, next_result_); + GetManager()->OnSoundEnd(kSessionIDForTests); + GetManager()->OnAudioEnd(kSessionIDForTests); + GetManager()->OnRecognitionResult(kSessionIDForTests, next_result_); + GetManager()->OnRecognitionEnd(kSessionIDForTests); } // Every test should leave the manager in the idle state when finished. diff --git a/chrome/browser/speech/speech_input_extension_manager.cc b/chrome/browser/speech/speech_input_extension_manager.cc index 047f1de..ed69815 100644 --- a/chrome/browser/speech/speech_input_extension_manager.cc +++ b/chrome/browser/speech/speech_input_extension_manager.cc @@ -15,7 +15,6 @@ #include "chrome/browser/profiles/profile_dependency_manager.h" #include "chrome/browser/profiles/profile_keyed_service.h" #include "chrome/browser/profiles/profile_keyed_service_factory.h" -#include "chrome/browser/speech/speech_recognition_tray_icon_controller.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/pref_names.h" @@ -23,9 +22,11 @@ #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/speech_recognition_manager.h" -#include "content/public/browser/speech_recognizer.h" +#include "content/public/browser/speech_recognition_session_config.h" +#include "content/public/browser/speech_recognition_session_context.h" #include "content/public/common/speech_recognition_error.h" #include "content/public/common/speech_recognition_result.h" +#include "net/url_request/url_request_context_getter.h" using content::BrowserThread; using content::SpeechRecognitionHypothesis; @@ -55,12 +56,6 @@ const char kOnResultEvent[] = "experimental.speechInput.onResult"; const char kOnSoundStartEvent[] = "experimental.speechInput.onSoundStart"; const char kOnSoundEndEvent[] = "experimental.speechInput.onSoundEnd"; -// Session id provided to the speech recognizer. Since only one extension can -// be recording on the same time a constant value is enough as id. -// TODO(primiano) this will not be valid anymore once speech input extension -// will use the SpeechRecognitionManager and not the SpeechRecognizer directly. -static const int kSpeechInputSessionId = 1; - // Wrap an SpeechInputExtensionManager using scoped_refptr to avoid // assertion failures on destruction because of not using release(). class SpeechInputExtensionManagerWrapper : public ProfileKeyedService { @@ -157,7 +152,10 @@ SpeechInputExtensionManager::SpeechInputExtensionManager(Profile* profile) : profile_(profile), state_(kIdle), registrar_(new content::NotificationRegistrar), - speech_interface_(NULL) { + speech_interface_(NULL), + is_recognition_in_progress_(false), + speech_recognition_session_id_( + SpeechRecognitionManager::kSessionIDInvalid) { registrar_->Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, content::Source<Profile>(profile_)); } @@ -194,11 +192,16 @@ void SpeechInputExtensionManager::ShutdownOnUIThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); VLOG(1) << "Profile shutting down."; + // Note: Unretained(this) is safe, also if we are freed in the meanwhile. + // It is used by the SR manager just for comparing the raw pointer and remove + // the associated sessions. + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&SpeechInputExtensionManager::AbortAllSessionsOnIOThread, + base::Unretained(this))); + base::AutoLock auto_lock(state_lock_); DCHECK(state_ != kShutdown); if (state_ != kIdle) { - DCHECK(notification_.get()); - notification_->Hide(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); } @@ -208,6 +211,18 @@ void SpeechInputExtensionManager::ShutdownOnUIThread() { profile_ = NULL; } +void SpeechInputExtensionManager::AbortAllSessionsOnIOThread() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + // TODO(primiano) The following check should not be really needed if the + // SpeechRecognitionManager and this class are destroyed in the correct order + // (this class first), as it is in current chrome implementation. + // However, it seems the some ChromiumOS tests violate the destruction order + // envisaged by browser_main_loop, so SpeechRecognitionmanager could have been + // freed by now. + if (SpeechRecognitionManager* mgr = SpeechRecognitionManager::GetInstance()) + mgr->AbortAllSessionsForListener(this); +} + void SpeechInputExtensionManager::ExtensionUnloaded( const std::string& extension_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -245,7 +260,7 @@ void SpeechInputExtensionManager::OnRecognitionResult( int session_id, const content::SpeechRecognitionResult& result) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK_EQ(session_id, kSpeechInputSessionId); + DCHECK_EQ(session_id, speech_recognition_session_id_); // Stopping will start the disassociation with the extension. // Make a copy to report the results to the proper one. @@ -288,13 +303,13 @@ void SpeechInputExtensionManager::SetRecognitionResultOnUIThread( } void SpeechInputExtensionManager::OnRecognitionStart(int session_id) { - DCHECK_EQ(session_id, kSpeechInputSessionId); + DCHECK_EQ(session_id, speech_recognition_session_id_); } void SpeechInputExtensionManager::OnAudioStart(int session_id) { VLOG(1) << "OnAudioStart"; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK_EQ(session_id, kSpeechInputSessionId); + DCHECK_EQ(session_id, speech_recognition_session_id_); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread, @@ -302,11 +317,16 @@ void SpeechInputExtensionManager::OnAudioStart(int session_id) { } void SpeechInputExtensionManager::OnAudioEnd(int session_id) { - DCHECK_EQ(session_id, kSpeechInputSessionId); } void SpeechInputExtensionManager::OnRecognitionEnd(int session_id) { - DCHECK_EQ(session_id, kSpeechInputSessionId); + // In the very exceptional case in which we requested a new recognition before + // the previous one ended, don't clobber the speech_recognition_session_id_. + if (speech_recognition_session_id_ == session_id) { + is_recognition_in_progress_ = false; + speech_recognition_session_id_ = + SpeechRecognitionManager::kSessionIDInvalid; + } } void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { @@ -320,21 +340,9 @@ void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { VLOG(1) << "State changed to recording"; state_ = kRecording; - const extensions::Extension* extension = profile_->GetExtensionService()-> - GetExtensionById(extension_id_in_use_, true); - DCHECK(extension); - - bool show_notification = !profile_->GetPrefs()->GetBoolean( - prefs::kSpeechInputTrayNotificationShown); - - if (!notification_.get()) - notification_ = new SpeechRecognitionTrayIconController(); - notification_->Show(UTF8ToUTF16(extension->name()), show_notification); - - if (show_notification) { - profile_->GetPrefs()->SetBoolean( - prefs::kSpeechInputTrayNotificationShown, true); - } + DCHECK(profile_); + profile_->GetPrefs()->SetBoolean( + prefs::kSpeechInputTrayNotificationShown, true); VLOG(1) << "Sending start notification"; content::NotificationService::current()->Notify( @@ -346,20 +354,13 @@ void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { void SpeechInputExtensionManager::OnRecognitionError( int session_id, const content::SpeechRecognitionError& error) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK_EQ(session_id, kSpeechInputSessionId); - - // Simply return in case of an ERROR_ABORTED, since it is not contemplated - // in the speech input extensions architecture. - if (error.code == content::SPEECH_RECOGNITION_ERROR_ABORTED) - return; - + DCHECK_EQ(session_id, speech_recognition_session_id_); VLOG(1) << "OnRecognitionError: " << error.code; base::AutoLock auto_lock(state_lock_); if (state_ == kShutdown) return; - // Release the recognizer object. GetSpeechInputExtensionInterface()->StopRecording(true); std::string event_error_code; @@ -369,6 +370,15 @@ void SpeechInputExtensionManager::OnRecognitionError( case content::SPEECH_RECOGNITION_ERROR_NONE: break; + case content::SPEECH_RECOGNITION_ERROR_ABORTED: + // ERROR_ABORTED is received whenever AbortSession is called on the + // manager. However, we want propagate the error only if it is triggered + // by an external cause (another recognition started, aborting us), thus + // only if it occurs while we are capturing audio. + if (state_ == kRecording) + event_error_code = kErrorCaptureError; + break; + case content::SPEECH_RECOGNITION_ERROR_AUDIO: if (state_ == kStarting) { event_error_code = kErrorUnableToStart; @@ -411,12 +421,12 @@ void SpeechInputExtensionManager::OnRecognitionError( void SpeechInputExtensionManager::OnEnvironmentEstimationComplete( int session_id) { - DCHECK_EQ(session_id, kSpeechInputSessionId); + DCHECK_EQ(session_id, speech_recognition_session_id_); } void SpeechInputExtensionManager::OnSoundStart(int session_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK_EQ(session_id, kSpeechInputSessionId); + DCHECK_EQ(session_id, speech_recognition_session_id_); VLOG(1) << "OnSoundStart"; std::string json_args; @@ -428,7 +438,6 @@ void SpeechInputExtensionManager::OnSoundStart(int session_id) { void SpeechInputExtensionManager::OnSoundEnd(int session_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK_EQ(session_id, kSpeechInputSessionId); VLOG(1) << "OnSoundEnd"; std::string json_args; @@ -471,11 +480,6 @@ void SpeechInputExtensionManager::DispatchError( if (state_ == kShutdown) return; - if (state_ == kRecording) { - DCHECK(notification_.get()); - notification_->Hide(); - } - extension_id = extension_id_in_use_; ResetToIdleState(); @@ -531,23 +535,37 @@ bool SpeechInputExtensionManager::Start( NOTREACHED(); } + const extensions::Extension* extension = profile_->GetExtensionService()-> + GetExtensionById(extension_id, true); + DCHECK(extension); + const string16& extension_name = UTF8ToUTF16(extension->name()); + extension_id_in_use_ = extension_id; VLOG(1) << "State changed to starting"; state_ = kStarting; + // Checks if the security notification balloon has been already shown (only + // once for a profile). It is reset on DidStartReceivingAudioOnUIThread. + scoped_refptr<net::URLRequestContextGetter> url_request_context_getter = + profile_->GetRequestContext(); + const bool show_notification = !profile_->GetPrefs()->GetBoolean( + prefs::kSpeechInputTrayNotificationShown); + BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this, - profile_->GetRequestContext(), language, grammar, - filter_profanities)); + url_request_context_getter, extension_name, language, grammar, + filter_profanities, show_notification)); return true; } void SpeechInputExtensionManager::StartOnIOThread( - net::URLRequestContextGetter* context_getter, + scoped_refptr<net::URLRequestContextGetter> context_getter, + const string16& extension_name, const std::string& language, const std::string& grammar, - bool filter_profanities) { + bool filter_profanities, + bool show_notification) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); VLOG(1) << "Requesting start (IO thread)"; @@ -558,6 +576,12 @@ void SpeechInputExtensionManager::StartOnIOThread( if (state_ == kShutdown) return; + // TODO(primiano) These two checks below could be avoided, since they are + // already handled in the speech recognition classes. However, since the + // speech input extensions tests are bypassing the manager, we need them to + // pass the tests. A complete unit test which puts all the pieces together, + // mocking just the endpoints (the audio input controller and the URL fetcher) + // should be written. if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -573,9 +597,13 @@ void SpeechInputExtensionManager::StartOnIOThread( return; } - GetSpeechInputExtensionInterface()->StartRecording( - this, context_getter, kSpeechInputSessionId, language, grammar, - filter_profanities); + GetSpeechInputExtensionInterface()->StartRecording(this, + context_getter, + extension_name, + language, + grammar, + filter_profanities, + show_notification); } bool SpeechInputExtensionManager::HasAudioInputDevices() { @@ -602,6 +630,7 @@ void SpeechInputExtensionManager::IsRecordingOnIOThread( DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); bool result = GetSpeechInputExtensionInterface()->IsCapturingAudio(); + BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread, @@ -618,20 +647,41 @@ void SpeechInputExtensionManager::IsRecordingOnUIThread( void SpeechInputExtensionManager::StartRecording( content::SpeechRecognitionEventListener* listener, net::URLRequestContextGetter* context_getter, - int session_id, + const string16& extension_name, const std::string& language, const std::string& grammar, - bool filter_profanities) { + bool filter_profanities, + bool show_notification) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK(!recognizer_); - recognizer_ = content::SpeechRecognizer::Create( - listener, session_id, language, grammar, context_getter, - filter_profanities, "", ""); - recognizer_->StartRecognition(); + + content::SpeechRecognitionSessionContext context; + context.requested_by_page_element = false; + context.is_first_request_for_context = show_notification; + context.context_name = extension_name; + + content::SpeechRecognitionSessionConfig config; + // config.is_one_shot = true; // TODO(primiano) Uncomment when CL2.0 lands. + config.language = language; + config.grammars.push_back(content::SpeechRecognitionGrammar(grammar)); + config.initial_context = context; + config.url_request_context_getter = context_getter; + config.filter_profanities = filter_profanities; + config.event_listener = listener; + + DCHECK(!is_recognition_in_progress_); + SpeechRecognitionManager& manager = *SpeechRecognitionManager::GetInstance(); + speech_recognition_session_id_ = + manager.CreateSession(config); + DCHECK_NE(speech_recognition_session_id_, + SpeechRecognitionManager::kSessionIDInvalid); + is_recognition_in_progress_ = true; + manager.StartSession(speech_recognition_session_id_); } bool SpeechInputExtensionManager::HasValidRecognizer() { - return !!recognizer_; + if (!is_recognition_in_progress_) + return false; + return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); } bool SpeechInputExtensionManager::Stop(const std::string& extension_id, @@ -692,13 +742,13 @@ void SpeechInputExtensionManager::ForceStopOnIOThread() { } void SpeechInputExtensionManager::StopRecording(bool recognition_failed) { - if (recognizer_) { - // Recognition is already cancelled in case of failure. - // Double-cancelling leads to assertion failures. - if (!recognition_failed) - recognizer_->AbortRecognition(); - recognizer_.release(); - } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!is_recognition_in_progress_) + return; + DCHECK_NE(speech_recognition_session_id_, + SpeechRecognitionManager::kSessionIDInvalid); + SpeechRecognitionManager::GetInstance()->AbortSession( + speech_recognition_session_id_); } void SpeechInputExtensionManager::StopSucceededOnUIThread() { @@ -712,9 +762,6 @@ void SpeechInputExtensionManager::StopSucceededOnUIThread() { std::string extension_id = extension_id_in_use_; ResetToIdleState(); - DCHECK(notification_.get()); - notification_->Hide(); - content::NotificationService::current()->Notify( chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED, // Guarded by the state_ == kShutdown check. @@ -723,18 +770,5 @@ void SpeechInputExtensionManager::StopSucceededOnUIThread() { } void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id, - float volume, - float noise_volume) { - DCHECK_EQ(session_id, kSpeechInputSessionId); - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&SpeechInputExtensionManager::SetInputVolumeOnUIThread, - this, volume)); -} - -void SpeechInputExtensionManager::SetInputVolumeOnUIThread( - float volume) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(notification_.get()); - notification_->SetVUMeterVolume(volume); -} + float volume, + float noise_volume) {} diff --git a/chrome/browser/speech/speech_input_extension_manager.h b/chrome/browser/speech/speech_input_extension_manager.h index 3923506..5045a35 100644 --- a/chrome/browser/speech/speech_input_extension_manager.h +++ b/chrome/browser/speech/speech_input_extension_manager.h @@ -11,18 +11,18 @@ #include "base/callback_forward.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" +#include "base/string16.h" #include "base/synchronization/lock.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/speech_recognition_event_listener.h" class Profile; -class SpeechRecognitionTrayIconController; namespace content { class NotificationRegistrar; struct SpeechRecognitionError; +class SpeechRecognitionManager; struct SpeechRecognitionResult; -class SpeechRecognizer; } namespace net { @@ -39,10 +39,11 @@ class SpeechInputExtensionInterface { virtual void StartRecording( content::SpeechRecognitionEventListener* listener, net::URLRequestContextGetter* context_getter, - int session_id, + const string16& extension_name, const std::string& language, const std::string& grammar, - bool filter_profanities) = 0; + bool filter_profanities, + bool show_notification) = 0; virtual void StopRecording(bool recognition_failed) = 0; virtual bool HasAudioInputDevices() = 0; @@ -143,19 +144,22 @@ class SpeechInputExtensionManager virtual void StartRecording( content::SpeechRecognitionEventListener* listener, net::URLRequestContextGetter* context_getter, - int session_id, + const string16& extension_name, const std::string& language, const std::string& grammar, - bool filter_profanities) OVERRIDE; + bool filter_profanities, + bool show_notification) OVERRIDE; virtual void StopRecording(bool recognition_failed) OVERRIDE; // Internal methods. void StartOnIOThread( - net::URLRequestContextGetter* context_getter, + scoped_refptr<net::URLRequestContextGetter> context_getter, + const string16& extension_name, const std::string& language, const std::string& grammar, - bool filter_profanities); + bool filter_profanities, + bool show_notification); void ForceStopOnIOThread(); void IsRecordingOnIOThread(const IsRecordingCallback& callback); @@ -172,9 +176,10 @@ class SpeechInputExtensionManager const std::string& json_args); void ExtensionUnloaded(const std::string& extension_id); - void SetInputVolumeOnUIThread(float volume); void ResetToIdleState(); + void AbortAllSessionsOnIOThread(); + virtual ~SpeechInputExtensionManager(); friend class base::RefCountedThreadSafe<SpeechInputExtensionManager>; @@ -196,10 +201,10 @@ class SpeechInputExtensionManager // Used in the UI thread. scoped_ptr<content::NotificationRegistrar> registrar_; SpeechInputExtensionInterface* speech_interface_; - scoped_refptr<SpeechRecognitionTrayIconController> notification_; // Used in the IO thread. - scoped_refptr<content::SpeechRecognizer> recognizer_; + bool is_recognition_in_progress_; + int speech_recognition_session_id_; }; #endif // CHROME_BROWSER_SPEECH_SPEECH_INPUT_EXTENSION_MANAGER_H_ |