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..1e1a55c5e76044283fb0973fdc229e343b80566d 100644 |
| --- a/chrome/browser/extensions/extension_speech_input_manager.cc |
| +++ b/chrome/browser/extensions/extension_speech_input_manager.cc |
| @@ -4,13 +4,25 @@ |
| #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 { |
| // Wrap an ExtensionSpeechInputManager using scoped_refptr to avoid |
| @@ -93,7 +105,7 @@ ProfileKeyedService* |
| ExtensionSpeechInputManager::ExtensionSpeechInputManager(Profile* profile) |
| : profile_(profile), |
| - extension_(NULL) { |
| + state_(kIdle) { |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| Source<Profile>(profile_)); |
| } |
| @@ -124,21 +136,369 @@ 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::SetRecognitionResult( |
| + int caller_id, |
| + const SpeechInputResult& result) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| + |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::SetRecognitionResultOnUIThread, |
| + this, result)); |
| +} |
| + |
| +void ExtensionSpeechInputManager::SetRecognitionResultOnUIThread( |
| + const SpeechInputResult& result) { |
| + 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(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; |
| + |
| + std::string event_error_code; |
| + switch (error) { |
| + case kErrorNone: |
| + break; |
| + |
| + case kErrorAudio: |
| + if (state_ == kStarting) { |
| + event_error_code = constants::kErrorUnableToStart; |
| + } 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)); |
| + } |
| +} |
| + |
| +void ExtensionSpeechInputManager::DidCompleteEnvironmentEstimation( |
| + int caller_id) { |
| + DCHECK_EQ(caller_id, kSpeechCallerId); |
| } |
| -void ExtensionSpeechInputManager::Start(const Extension *extension) { |
| - // TODO(leandrogracia): Start speech recognition. |
| +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, std::string(constants::kOnSoundStartEvent), json_args)); |
| } |
| -void ExtensionSpeechInputManager::Stop(const Extension *extension) { |
| - // TODO(leandrogracia): Stop speech recognition. |
| +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, std::string(constants::kOnSoundEndEvent), json_args)); |
| } |
| +void ExtensionSpeechInputManager::DispatchEventToExtension( |
| + 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_in_use_, event, final_args, profile_, GURL()); |
| + } |
| +} |
| + |
| +void ExtensionSpeechInputManager::DispatchError( |
| + const std::string& error) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + { |
| + base::AutoLock auto_lock(state_lock_); |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + ExtensionError details(extension_id_in_use_, error); |
| + NotificationService::current()->Notify( |
| + chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED, |
| + Source<Profile>(profile_), |
| + Details<ExtensionError>(&details)); |
| + } |
| + |
| + 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(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; |
| + } |
| + |
| + if (state_ != kIdle) { |
| + *error = constants::kErrorRequestOngoing; |
|
Satish
2011/10/11 13:37:16
'RequestOngoing' - This error message is cryptic.
Leandro Graciá Gil
2011/10/11 18:08:00
Done.
|
| + return false; |
| + } |
| + |
| + 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 (!AudioManager::GetAudioManager()->HasAudioInputDevices()) { |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::DispatchError, this, |
| + std::string(constants::kErrorNoDevicesAvailable))); |
| + return; |
| + } |
| + |
| + recognizer_ = new SpeechRecognizer(this, kSpeechCallerId, language, grammar, |
| + context_getter, filter_profanities, "", ""); |
| + recognizer_->StartRecording(); |
| +} |
| + |
| +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; |
| + } |
| + |
| + if (state_ != kRecording) { |
| + *error = constants::kErrorRequestOngoing; |
|
Satish
2011/10/11 13:37:16
Is this the correct error to return when Stop is c
Leandro Graciá Gil
2011/10/11 18:08:00
Fixed in later modifications of the code.
On 2011
|
| + return false; |
| + } |
| + |
| + DCHECK(recognizer_); |
| + |
| + 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); |
| + |
| + if (recognizer_) { |
| + recognizer_->CancelRecognition(); |
| + recognizer_.release(); |
| + } |
| + |
| + if (state_ == kShutdown) |
| + return; |
| + |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&ExtensionSpeechInputManager::StopSucceededOnUIThread, this)); |
| +} |
| + |
| +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(); |
| +} |