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

Unified Diff: chrome/browser/extensions/extension_speech_input_manager.cc

Issue 8201030: Introducing the basic functionality to the speech input experimental API. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: review fixes. Created 9 years, 2 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: 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..8405c81478266387ce6310893df34f60f6125ca8 100644
--- a/chrome/browser/extensions/extension_speech_input_manager.cc
+++ b/chrome/browser/extensions/extension_speech_input_manager.cc
@@ -4,13 +4,23 @@
#include "chrome/browser/extensions/extension_speech_input_manager.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
@@ -57,9 +67,16 @@ class ExtensionSpeechInputManager::Factory : public ProfileKeyedServiceFactory {
virtual bool ServiceIsNULLWhileTesting() { return true; }
virtual bool ServiceIsCreatedWithProfile() { return true; }
+ // Since speech input extension API managers are created per-profile,
+ // a unique id is required for each of them to avoid mixing results
+ // from extensions being used on different profiles simultaneously.
+ static int per_profile_id_;
Satish 2011/10/09 20:01:47 The caller id give to SpeechRecognizer is useful o
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+
DISALLOW_COPY_AND_ASSIGN(Factory);
};
+int ExtensionSpeechInputManager::Factory::per_profile_id_ = 0;
+
void ExtensionSpeechInputManager::Factory::Initialize() {
GetInstance();
}
@@ -87,13 +104,15 @@ ProfileKeyedService*
ExtensionSpeechInputManager::Factory::BuildServiceInstanceFor(
Profile* profile) const {
scoped_refptr<ExtensionSpeechInputManager> manager(
- new ExtensionSpeechInputManager(profile));
+ new ExtensionSpeechInputManager(profile, ++per_profile_id_));
return new ExtensionSpeechInputManagerWrapper(manager);
}
-ExtensionSpeechInputManager::ExtensionSpeechInputManager(Profile* profile)
+ExtensionSpeechInputManager::ExtensionSpeechInputManager(Profile* profile,
+ int speech_caller_id)
: profile_(profile),
- extension_(NULL) {
+ speech_caller_id_(speech_caller_id),
+ state_(kIdle) {
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
Source<Profile>(profile_));
}
@@ -124,21 +143,385 @@ 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.";
+
+ state_lock_.Acquire();
Satish 2011/10/09 20:01:47 use base::AutoLock instead of acquiring and releas
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ DCHECK(state_ != kShutdown);
+ if (state_ != kIdle) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExtensionSpeechInputManager::ForceStopOnIOThread));
+ }
+ state_ = kShutdown;
+ VLOG(1) << "Entering the shutdown sink state.";
registrar_.RemoveAll();
profile_ = NULL;
+ state_lock_.Release();
+}
+
+bool ExtensionSpeechInputManager::ShuttingDown() {
+ state_lock_.Acquire();
+ bool shutting_down = state_ == kShutdown;
+ state_lock_.Release();
+ return shutting_down;
}
void ExtensionSpeechInputManager::ExtensionUnloaded(const std::string& id) {
- // TODO(leandrogracia): Force stop to speech recognition if the extension
- // is currently using the API.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ VLOG(1) << "Extension unloaded. Requesting to enforce stop...";
Satish 2011/10/09 20:01:47 should this be after the below if block?
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ state_lock_.Acquire();
+ if (state_ == kShutdown) {
+ state_lock_.Release();
+ return;
+ }
+
+ if (!extension_id_.empty() && extension_id_ == id) {
Satish 2011/10/09 20:01:47 is the .empty() check required since it is a strin
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ extension_id_.clear();
Satish 2011/10/09 20:01:47 You are clearing this id here but in ForceStopOnIO
Leandro Graciá Gil 2011/10/10 18:59:38 Now clearing only on ForceStopOnIOThread. Fixed.
+
+ if (state_ != kIdle) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExtensionSpeechInputManager::ForceStopOnIOThread));
+ }
+ }
+ state_lock_.Release();
+}
+
+void ExtensionSpeechInputManager::SetRecognitionResult(
+ int caller_id,
+ const SpeechInputResult& result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(caller_id, speech_caller_id_);
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExtensionSpeechInputManager::SetRecognitionResultOnUIThread, result));
Satish 2011/10/09 20:01:47 You may need to convert all NewRunnableMethod call
Leandro Graciá Gil 2011/10/10 18:59:38 Fixed here. I'll prepare a CL for the speech input
+}
+
+void ExtensionSpeechInputManager::SetRecognitionResultOnUIThread(
+ const SpeechInputResult& result) {
Satish 2011/10/09 20:01:47 fix indentation
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ state_lock_.Acquire();
+ if (state_ == kShutdown) {
+ state_lock_.Release();
+ return;
+ }
+ state_ = kIdle;
+ state_lock_.Release();
+
+ 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, speech_caller_id_);
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExtensionSpeechInputManager::DidStartReceivingAudioOnUIThread));
+}
+
+void ExtensionSpeechInputManager::DidCompleteRecording(int caller_id) {
+ VLOG(1) << "DidCompleteRecording";
Satish 2011/10/09 20:01:47 if you don't intend to add any code here and the n
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ DCHECK_EQ(caller_id, speech_caller_id_);
+}
+
+void ExtensionSpeechInputManager::DidCompleteRecognition(int caller_id) {
+ VLOG(1) << "DidCompleteRecognition";
+ DCHECK_EQ(caller_id, speech_caller_id_);
+}
+
+void ExtensionSpeechInputManager::DidStartReceivingAudioOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ state_lock_.Acquire();
+ if (state_ == kShutdown) {
+ state_lock_.Release();
+ return;
+ }
+
+ DCHECK_EQ(state_, kStarting);
+ VLOG(1) << "State changed to recording";
+ state_ = kRecording;
+ state_lock_.Release();
+
+ VLOG(1) << "Sending start notification";
+ NotificationService::current()->Notify(
+ chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED,
+ Source<Profile>(profile_),
+ Details<std::string>(&extension_id_));
+}
+
+void ExtensionSpeechInputManager::OnRecognizerError(
+ int caller_id, SpeechInputError error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(caller_id, speech_caller_id_);
+ VLOG(1) << "OnRecognizerError: " << error;
+
+ state_lock_.Acquire();
+ if (state_ == kShutdown) {
+ state_lock_.Release();
+ return;
+ }
+
+ std::string event_error_code;
+ switch (error) {
+ case kErrorNone:
Satish 2011/10/09 20:01:47 fix indentation of case statements
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ break;
+
+ case kErrorAudio:
+ if (state_ == kStarting)
Satish 2011/10/09 20:01:47 use braces
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ 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;
+ state_lock_.Release();
+
+ if (!event_error_code.empty())
Satish 2011/10/09 20:01:47 ditto
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExtensionSpeechInputManager::DispatchError, event_error_code));
+}
+
+void ExtensionSpeechInputManager::DidCompleteEnvironmentEstimation(
+ int caller_id) {
+ VLOG(1) << "DidCompleteEnvironmentEstimation";
+ DCHECK_EQ(caller_id, speech_caller_id_);
+}
+
+void ExtensionSpeechInputManager::DidStartReceivingSpeech(int caller_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(caller_id, speech_caller_id_);
+ VLOG(1) << "DidStartReceivingSpeech";
+
+ ListValue args;
+ std::string json_args;
+ base::JSONWriter::Write(&args, false, &json_args);
Satish 2011/10/09 20:01:47 is it really required to send an empty list as arg
Leandro Graciá Gil 2011/10/10 18:59:38 It is required to send the empty list, otherwise y
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExtensionSpeechInputManager::DispatchEventToExtension,
+ std::string(constants::kOnSoundStartEvent), json_args));
}
-void ExtensionSpeechInputManager::Start(const Extension *extension) {
- // TODO(leandrogracia): Start speech recognition.
+void ExtensionSpeechInputManager::DidStopReceivingSpeech(int caller_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(caller_id, speech_caller_id_);
+ VLOG(1) << "DidStopReceivingSpeech";
+
+ ListValue args;
+ std::string json_args;
+ base::JSONWriter::Write(&args, false, &json_args);
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExtensionSpeechInputManager::DispatchEventToExtension,
+ std::string(constants::kOnSoundEndEvent), json_args));
}
-void ExtensionSpeechInputManager::Stop(const Extension *extension) {
- // TODO(leandrogracia): Stop speech recognition.
+void ExtensionSpeechInputManager::DispatchEventToExtension(
+ const std::string& event, const std::string& json_args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (ShuttingDown())
+ return;
+
+ if (profile_ && profile_->GetExtensionEventRouter())
Satish 2011/10/09 20:01:47 use braces
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ profile_->GetExtensionEventRouter()->DispatchEventToExtension(
+ extension_id_, event, json_args, profile_, GURL());
+}
+
+void ExtensionSpeechInputManager::DispatchError(const std::string& code) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (ShuttingDown())
+ return;
+
+ NotificationService::current()->Notify(
+ chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_FAILED,
+ Source<Profile>(profile_),
+ Details<std::string>(&extension_id_));
+
+ ListValue args;
+ DictionaryValue *js_error = new DictionaryValue();
+ args.Append(js_error);
+ js_error->SetString(constants::kErrorCodeKey, code);
+ std::string json_args;
+ base::JSONWriter::Write(&args, false, &json_args);
+ DispatchEventToExtension(constants::kOnErrorEvent, json_args);
+}
+
+void ExtensionSpeechInputManager::Start(const std::string& extension_id,
+ const std::string& language, const std::string& grammar,
+ bool filter_profanities) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(1) << "Requesting start (UI thread)";
+
+ if (ShuttingDown())
+ return;
+
+ if (!extension_id_.empty() && extension_id != extension_id_) {
Satish 2011/10/09 20:01:47 is the .empty check required as asked above?
Leandro Graciá Gil 2011/10/10 18:59:38 Here it is, otherwise it will generate an error ev
+ DispatchError(constants::kErrorRequestDenied);
+ return;
+ }
+
+ state_lock_.Acquire();
+ if (state_ != kIdle) {
Satish 2011/10/09 20:01:47 could be simpler to merge this check with the 'if
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ DCHECK(recognizer_);
+ state_lock_.Release();
+ return;
+ }
+
+ extension_id_ = extension_id;
+ VLOG(1) << "Start";
+
+ VLOG(1) << "State changed to starting";
+ state_ = kStarting;
+ state_lock_.Release();
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ NewRunnableMethod(this, &ExtensionSpeechInputManager::StartOnIOThread,
+ profile_->GetRequestContext(), language, grammar, filter_profanities));
+}
+
+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.
+ state_lock_.Acquire();
+ if (state_ == kShutdown) {
+ state_lock_.Release();
+ return;
+ }
+
+ if (!AudioManager::GetAudioManager()->HasAudioInputDevices()) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this, &ExtensionSpeechInputManager::DispatchError,
+ std::string(constants::kErrorNoDevicesAvailable)));
+ state_lock_.Release();
+ return;
+ }
+
+ recognizer_ = new SpeechRecognizer(this, speech_caller_id_, language, grammar,
+ context_getter, filter_profanities, "", "");
+ recognizer_->StartRecording();
+ state_lock_.Release();
+}
+
+void ExtensionSpeechInputManager::Stop(const std::string& extension_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(1) << "Requesting stop (UI thread)";
+
+ if (ShuttingDown())
+ return;
+
+ if (!extension_id_.empty() && extension_id != extension_id_) {
+ DispatchError(constants::kErrorRequestDenied);
+ return;
+ }
+
+ state_lock_.Acquire();
+ if (state_ != kRecording) {
Satish 2011/10/09 20:01:47 merge this with the first if statement here?
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ state_lock_.Release();
+ return;
+ }
+
+ DCHECK(recognizer_);
+
+ VLOG(1) << "State changed to stopping";
+ state_ = kStopping;
+ state_lock_.Release();
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExtensionSpeechInputManager::ForceStopOnIOThread));
+}
+
+void ExtensionSpeechInputManager::ForceStopOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ VLOG(1) << "Requesting forced stop (IO thread)";
+
+ state_lock_.Acquire();
+ DCHECK(state_ != kIdle);
+
+ if (recognizer_) {
+ recognizer_->CancelRecognition();
+ recognizer_ = NULL;
Satish 2011/10/09 20:01:47 recognizer_.release() ?
Leandro Graciá Gil 2011/10/10 18:59:38 Done.
+ }
+
+ if (state_ == kShutdown) {
+ state_lock_.Release();
+ 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_));
+
+ extension_id_.clear();
+ state_lock_.Release();
}

Powered by Google App Engine
This is Rietveld 408576698