Chromium Code Reviews| Index: chrome/browser/extensions/extension_speech_input_manager.cc |
| diff --git a/chrome/browser/extensions/extension_speech_input_manager.cc b/chrome/browser/extensions/extension_speech_input_manager.cc |
| index fb4ae7914a14e41810b385373894655f3d3e57d5..c114df6b0877705deaa9853f607a9b75522dc72e 100644 |
| --- a/chrome/browser/extensions/extension_speech_input_manager.cc |
| +++ b/chrome/browser/extensions/extension_speech_input_manager.cc |
| @@ -4,15 +4,31 @@ |
| #include "chrome/browser/extensions/extension_speech_input_manager.h" |
| +#include "base/bind.h" |
| +#include "base/json/json_writer.h" |
| +#include "base/utf_string_conversions.h" |
| +#include "base/values.h" |
| +#include "chrome/browser/extensions/extension_event_router.h" |
| +#include "chrome/browser/extensions/extension_speech_input_api_constants.h" |
| +#include "chrome/browser/profiles/profile.h" |
| #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/common/chrome_notification_types.h" |
| #include "chrome/common/extensions/extension.h" |
| +#include "content/browser/browser_thread.h" |
| #include "content/common/notification_service.h" |
| +using namespace speech_input; |
| + |
| +namespace constants = extension_speech_input_api_constants; |
| + |
| namespace { |
| +// Caller id provided to the speech recognizer. Since only one extension can |
| +// be recording on the same time a constant value is enough as id. |
| +static const int kSpeechCallerId = 1; |
| + |
| // Wrap an ExtensionSpeechInputManager using scoped_refptr to avoid |
| // assertion failures on destruction because of not using release(). |
| class ExtensionSpeechInputManagerWrapper : public ProfileKeyedService { |
| @@ -52,7 +68,7 @@ class ExtensionSpeechInputManager::Factory : public ProfileKeyedServiceFactory { |
| // ProfileKeyedServiceFactory methods: |
| virtual ProfileKeyedService* BuildServiceInstanceFor( |
| - Profile* profile) const; |
| + Profile* profile) const OVERRIDE; |
| virtual bool ServiceRedirectedInIncognito() { return false; } |
|
Leandro GraciĆ” Gil
2011/10/11 18:10:51
Adding OVERRIDE to these.
|
| virtual bool ServiceIsNULLWhileTesting() { return true; } |
| virtual bool ServiceIsCreatedWithProfile() { return true; } |
| @@ -93,7 +109,8 @@ ProfileKeyedService* |
| ExtensionSpeechInputManager::ExtensionSpeechInputManager(Profile* profile) |
| : profile_(profile), |
| - extension_(NULL) { |
| + state_(kIdle), |
| + speech_interface_(NULL) { |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| Source<Profile>(profile_)); |
| } |
| @@ -124,21 +141,448 @@ void ExtensionSpeechInputManager::Observe(int type, |
| } |
| void ExtensionSpeechInputManager::ShutdownOnUIThread() { |
| - // TODO(leandrogracia): Force stop to speech recognition if active. |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + VLOG(1) << "Profile shutting down."; |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + DCHECK(state_ != kShutdown); |
| + if (state_ != kIdle) { |
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::ForceStopOnIOThread, this)); |
| + } |
| + state_ = kShutdown; |
| + VLOG(1) << "Entering the shutdown sink state."; |
| registrar_.RemoveAll(); |
| profile_ = NULL; |
| } |
| -void ExtensionSpeechInputManager::ExtensionUnloaded(const std::string& id) { |
| - // TODO(leandrogracia): Force stop to speech recognition if the extension |
| - // is currently using the API. |
| +void ExtensionSpeechInputManager::ExtensionUnloaded( |
| + const std::string& extension_id) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + VLOG(1) << "Extension unloaded. Requesting to enforce stop..."; |
| + if (extension_id_in_use_ == extension_id) { |
| + if (state_ != kIdle) { |
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::ForceStopOnIOThread, this)); |
| + } |
| + } |
| +} |
| + |
| +void ExtensionSpeechInputManager::SetExtensionSpeechInterface( |
| + ExtensionSpeechInterface* interface) { |
| + speech_interface_ = interface; |
| } |
| -void ExtensionSpeechInputManager::Start(const Extension *extension) { |
| - // TODO(leandrogracia): Start speech recognition. |
| +ExtensionSpeechInterface* |
| + ExtensionSpeechInputManager::GetExtensionSpeechInterface() { |
| + return speech_interface_ ? speech_interface_ : this; |
| } |
| -void ExtensionSpeechInputManager::Stop(const Extension *extension) { |
| - // TODO(leandrogracia): Stop speech recognition. |
| +void ExtensionSpeechInputManager::SetRecognitionResult( |
| + int caller_id, |
| + const SpeechInputResult& result) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| + |
| + // Stopping will start the disassociation with the extension. |
| + // Make a copy to report the results to the proper one. |
| + std::string extension_id = extension_id_in_use_; |
| + ForceStopOnIOThread(); |
| + |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::SetRecognitionResultOnUIThread, |
| + this, result, extension_id)); |
| } |
| +void ExtensionSpeechInputManager::SetRecognitionResultOnUIThread( |
| + const SpeechInputResult& result, const std::string& extension_id) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + { |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + state_ = kIdle; |
| + } |
| + |
| + ListValue args; |
| + DictionaryValue* js_event = new DictionaryValue(); |
| + args.Append(js_event); |
| + |
| + ListValue* js_hypothesis_array = new ListValue(); |
| + js_event->Set(constants::kHypothesesKey, js_hypothesis_array); |
| + |
| + for (size_t i = 0; i < result.hypotheses.size(); ++i) { |
| + const SpeechInputHypothesis& hypothesis = result.hypotheses[i]; |
| + |
| + DictionaryValue* js_hypothesis_object = new DictionaryValue(); |
| + js_hypothesis_array->Append(js_hypothesis_object); |
| + |
| + js_hypothesis_object->SetString(constants::kUtteranceKey, |
| + UTF16ToUTF8(hypothesis.utterance)); |
| + js_hypothesis_object->SetDouble(constants::kConfidenceKey, |
| + hypothesis.confidence); |
| + } |
| + |
| + std::string json_args; |
| + base::JSONWriter::Write(&args, false, &json_args); |
| + VLOG(1) << "Results: " << json_args; |
| + DispatchEventToExtension(extension_id, constants::kOnResultEvent, json_args); |
| +} |
| + |
| +void ExtensionSpeechInputManager::DidStartReceivingAudio(int caller_id) { |
| + VLOG(1) << "DidStartReceivingAudio"; |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| + |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::DidStartReceivingAudioOnUIThread, |
| + this)); |
| +} |
| + |
| +void ExtensionSpeechInputManager::DidCompleteRecording(int caller_id) { |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| +} |
| + |
| +void ExtensionSpeechInputManager::DidCompleteRecognition(int caller_id) { |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| +} |
| + |
| +void ExtensionSpeechInputManager::DidStartReceivingAudioOnUIThread() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + DCHECK_EQ(state_, kStarting); |
| + VLOG(1) << "State changed to recording"; |
| + state_ = kRecording; |
| + |
| + VLOG(1) << "Sending start notification"; |
| + NotificationService::current()->Notify( |
| + chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED, |
| + Source<Profile>(profile_), |
| + Details<std::string>(&extension_id_in_use_)); |
| +} |
| + |
| +void ExtensionSpeechInputManager::OnRecognizerError( |
| + int caller_id, SpeechInputError error) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| + VLOG(1) << "OnRecognizerError: " << error; |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + // Release the recognizer object. |
| + GetExtensionSpeechInterface()->StopRecording(true); |
| + |
| + std::string event_error_code; |
| + bool report_to_event = true; |
| + |
| + switch (error) { |
| + case kErrorNone: |
| + break; |
| + |
| + case kErrorAudio: |
| + if (state_ == kStarting) { |
| + event_error_code = constants::kErrorUnableToStart; |
| + report_to_event = false; |
| + } else { |
| + event_error_code = constants::kErrorCaptureError; |
| + } |
| + break; |
| + |
| + case kErrorNetwork: |
| + event_error_code = constants::kErrorNetworkError; |
| + break; |
| + |
| + case kErrorBadGrammar: |
| + // No error is returned on invalid language, for example. |
| + // To avoid confusion about when this is would be fired, the invalid |
| + // params error is not being exposed to the onError event. |
| + event_error_code = constants::kErrorUnableToStart; |
| + break; |
| + |
| + case kErrorNoSpeech: |
| + event_error_code = constants::kErrorNoSpeechHeard; |
| + break; |
| + |
| + case kErrorNoMatch: |
| + event_error_code = constants::kErrorNoResults; |
| + break; |
| + |
| + // The remaining kErrorAborted case should never be returned by the server. |
| + default: |
| + NOTREACHED(); |
| + } |
| + |
| + VLOG(1) << "State changed to idle"; |
| + state_ = kIdle; |
| + |
| + if (!event_error_code.empty()) { |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::DispatchError, |
| + this, event_error_code, report_to_event)); |
| + } |
| +} |
| + |
| +void ExtensionSpeechInputManager::DidCompleteEnvironmentEstimation( |
| + int caller_id) { |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| +} |
| + |
| +void ExtensionSpeechInputManager::DidStartReceivingSpeech(int caller_id) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| + VLOG(1) << "DidStartReceivingSpeech"; |
| + |
| + std::string json_args; |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::DispatchEventToExtension, |
| + this, extension_id_in_use_, std::string(constants::kOnSoundStartEvent), |
| + json_args)); |
| +} |
| + |
| +void ExtensionSpeechInputManager::DidStopReceivingSpeech(int caller_id) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| + VLOG(1) << "DidStopReceivingSpeech"; |
| + |
| + std::string json_args; |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::DispatchEventToExtension, |
| + this, extension_id_in_use_, std::string(constants::kOnSoundEndEvent), |
| + json_args)); |
| +} |
| + |
| +void ExtensionSpeechInputManager::DispatchEventToExtension( |
| + const std::string& extension_id, const std::string& event, |
| + const std::string& json_args) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + if (profile_ && profile_->GetExtensionEventRouter()) { |
| + std::string final_args; |
| + if (json_args.empty()) { |
| + ListValue args; |
| + base::JSONWriter::Write(&args, false, &final_args); |
| + } else { |
| + final_args = json_args; |
| + } |
| + |
| + profile_->GetExtensionEventRouter()->DispatchEventToExtension( |
| + extension_id, event, final_args, profile_, GURL()); |
| + } |
| +} |
| + |
| +void ExtensionSpeechInputManager::DispatchError( |
| + const std::string& error, bool dispatch_event) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + { |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + // Will set the error property in the ongoing extension function calls. |
| + ExtensionError details(extension_id_in_use_, error); |
| + NotificationService::current()->Notify( |
| + chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED, |
| + Source<Profile>(profile_), |
| + Details<ExtensionError>(&details)); |
| + } |
| + |
| + // Used for errors that are also reported via the onError event. |
| + if (dispatch_event) { |
| + ListValue args; |
| + DictionaryValue *js_error = new DictionaryValue(); |
| + args.Append(js_error); |
| + js_error->SetString(constants::kErrorCodeKey, error); |
| + std::string json_args; |
| + base::JSONWriter::Write(&args, false, &json_args); |
| + DispatchEventToExtension(extension_id_in_use_, |
| + constants::kOnErrorEvent, json_args); |
| + } |
| +} |
| + |
| +bool ExtensionSpeechInputManager::Start(const std::string& extension_id, |
| + const std::string& language, const std::string& grammar, |
| + bool filter_profanities, std::string* error) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(error); |
| + VLOG(1) << "Requesting start (UI thread)"; |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown || |
| + (!extension_id_in_use_.empty() && extension_id_in_use_ != extension_id)) { |
| + *error = constants::kErrorRequestDenied; |
| + return false; |
| + } |
| + |
| + switch (state_) { |
| + case kIdle: |
| + break; |
| + |
| + case kStarting: |
| + *error = constants::kErrorRequestInProgress; |
| + return false; |
| + |
| + case kRecording: |
| + case kStopping: |
| + *error = constants::kErrorInvalidOperation; |
| + return false; |
| + |
| + default: |
| + NOTREACHED(); |
| + } |
| + |
| + extension_id_in_use_ = extension_id; |
| + VLOG(1) << "State changed to starting"; |
| + state_ = kStarting; |
| + |
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::StartOnIOThread, this, |
| + profile_->GetRequestContext(), language, grammar, filter_profanities)); |
| + return true; |
| +} |
| + |
| +void ExtensionSpeechInputManager::StartOnIOThread( |
| + net::URLRequestContextGetter* context_getter, |
| + const std::string& language, const std::string& grammar, |
| + bool filter_profanities) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + VLOG(1) << "Requesting start (IO thread)"; |
| + |
| + // Everything put inside the lock to ensure the validity of context_getter, |
| + // guaranteed while not in the shutdown state. Any ongoing or recognition |
| + // request will be requested to be aborted when entering the shutdown state. |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + if (!GetExtensionSpeechInterface()->CheckForAvailableDevices()) { |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::DispatchError, this, |
| + std::string(constants::kErrorNoDevicesAvailable), false)); |
| + return; |
| + } |
| + |
| + GetExtensionSpeechInterface()->StartRecording(this, context_getter, |
| + kSpeechCallerId, language, grammar, filter_profanities); |
| +} |
| + |
| +bool ExtensionSpeechInputManager::CheckForAvailableDevices() { |
| + return AudioManager::GetAudioManager()->HasAudioInputDevices(); |
| +} |
| + |
| +void ExtensionSpeechInputManager::StartRecording( |
| + speech_input::SpeechRecognizerDelegate* delegate, |
| + net::URLRequestContextGetter* context_getter, int caller_id, |
| + const std::string& language, const std::string& grammar, |
| + bool filter_profanities) { |
| + DCHECK(!recognizer_); |
| + recognizer_ = new SpeechRecognizer(delegate, caller_id, language, grammar, |
| + context_getter, filter_profanities, "", ""); |
| + recognizer_->StartRecording(); |
| +} |
| + |
| +bool ExtensionSpeechInputManager::HasValidRecognizer() const { |
| + return recognizer_; |
| +} |
| + |
| +bool ExtensionSpeechInputManager::Stop(const std::string& extension_id, |
| + std::string* error) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(error); |
| + VLOG(1) << "Requesting stop (UI thread)"; |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown || |
| + (!extension_id_in_use_.empty() && extension_id_in_use_ != extension_id)) { |
| + *error = constants::kErrorRequestDenied; |
| + return false; |
| + } |
| + |
| + switch (state_) { |
| + case kRecording: |
| + break; |
| + |
| + case kStopping: |
| + *error = constants::kErrorRequestInProgress; |
| + return false; |
| + |
| + case kIdle: |
| + case kStarting: |
| + *error = constants::kErrorInvalidOperation; |
| + return false; |
| + |
| + default: |
| + NOTREACHED(); |
| + } |
| + |
| + // Guarded by the state lock. |
| + DCHECK(GetExtensionSpeechInterface()->HasValidRecognizer()); |
| + |
| + VLOG(1) << "State changed to stopping"; |
| + state_ = kStopping; |
| + |
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::ForceStopOnIOThread, this)); |
| + return true; |
| +} |
| + |
| +void ExtensionSpeechInputManager::ForceStopOnIOThread() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + VLOG(1) << "Requesting forced stop (IO thread)"; |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + DCHECK(state_ != kIdle); |
| + |
| + GetExtensionSpeechInterface()->StopRecording(false); |
| + |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::StopSucceededOnUIThread, this)); |
| +} |
| + |
| +void ExtensionSpeechInputManager::StopRecording(bool recognition_failed) { |
| + if (recognizer_) { |
| + // Recognition is already cancelled in case of failure. |
| + // Double-cancelling leads to assertion failures. |
| + if (!recognition_failed) |
| + recognizer_->CancelRecognition(); |
| + recognizer_.release(); |
| + } |
| +} |
| + |
| +void ExtensionSpeechInputManager::StopSucceededOnUIThread() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + VLOG(1) << "Stop succeeded (UI thread)"; |
| + |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + state_ = kIdle; |
| + VLOG(1) << "State changed to idle"; |
| + |
| + NotificationService::current()->Notify( |
| + chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED, |
| + Source<Profile>(profile_), // Guarded by the state_ == kShutdown check. |
| + Details<std::string>(&extension_id_in_use_)); |
| + |
| + extension_id_in_use_.clear(); |
| +} |