blob: 829563c019b4f9596d4941f82fc822e8e0fe6756 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/speech/speech_input_extension_manager.h"
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_function_registry.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.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/browser/speech/speech_input_extension_api.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/speech_recognition_manager.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;
using content::SpeechRecognitionManager;
namespace {
const char kErrorNoRecordingDeviceFound[] = "noRecordingDeviceFound";
const char kErrorRecordingDeviceInUse[] = "recordingDeviceInUse";
const char kErrorUnableToStart[] = "unableToStart";
const char kErrorRequestDenied[] = "requestDenied";
const char kErrorRequestInProgress[] = "requestInProgress";
const char kErrorInvalidOperation[] = "invalidOperation";
const char kErrorCodeKey[] = "code";
const char kErrorCaptureError[] = "captureError";
const char kErrorNetworkError[] = "networkError";
const char kErrorNoSpeechHeard[] = "noSpeechHeard";
const char kErrorNoResults[] = "noResults";
const char kUtteranceKey[] = "utterance";
const char kConfidenceKey[] = "confidence";
const char kHypothesesKey[] = "hypotheses";
const char kOnErrorEvent[] = "experimental.speechInput.onError";
const char kOnResultEvent[] = "experimental.speechInput.onResult";
const char kOnSoundStartEvent[] = "experimental.speechInput.onSoundStart";
const char kOnSoundEndEvent[] = "experimental.speechInput.onSoundEnd";
}
SpeechInputExtensionInterface::SpeechInputExtensionInterface() {
}
SpeechInputExtensionInterface::~SpeechInputExtensionInterface() {
}
SpeechInputExtensionManager::SpeechInputExtensionManager(Profile* profile)
: profile_(profile),
state_(kIdle),
registrar_(new content::NotificationRegistrar),
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_));
}
SpeechInputExtensionManager::~SpeechInputExtensionManager() {
}
SpeechInputExtensionManager* SpeechInputExtensionManager::GetForProfile(
Profile* profile) {
extensions::SpeechInputAPI* speech_input_api =
extensions::ProfileKeyedAPIFactory<extensions::SpeechInputAPI>::
GetForProfile(profile);
if (!speech_input_api)
return NULL;
return speech_input_api->manager();
}
void SpeechInputExtensionManager::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
ExtensionUnloaded(
content::Details<extensions::UnloadedExtensionInfo>(details)->
extension->id());
} else {
NOTREACHED();
}
}
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) {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this));
}
state_ = kShutdown;
VLOG(1) << "Entering the shutdown sink state.";
registrar_.reset();
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));
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(&SpeechInputExtensionManager::ForceStopOnIOThread, this));
}
}
}
void SpeechInputExtensionManager::SetSpeechInputExtensionInterface(
SpeechInputExtensionInterface* speech_interface) {
speech_interface_ = speech_interface;
}
SpeechInputExtensionInterface*
SpeechInputExtensionManager::GetSpeechInputExtensionInterface() {
return speech_interface_ ? speech_interface_ : this;
}
void SpeechInputExtensionManager::ResetToIdleState() {
VLOG(1) << "State changed to idle. Deassociating any extensions.";
state_ = kIdle;
extension_id_in_use_.clear();
}
int SpeechInputExtensionManager::GetRenderProcessIDForExtension(
const std::string& extension_id) const {
ExtensionProcessManager* epm =
extensions::ExtensionSystem::Get(profile_)->process_manager();
DCHECK(epm);
extensions::ExtensionHost* eh =
epm->GetBackgroundHostForExtension(extension_id);
DCHECK(eh);
content::RenderProcessHost* rph = eh->render_process_host();
DCHECK(rph);
return rph->GetID();
}
void SpeechInputExtensionManager::OnRecognitionResults(
int session_id,
const content::SpeechRecognitionResults& results) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
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.
std::string extension_id = extension_id_in_use_;
ForceStopOnIOThread();
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::SetRecognitionResultsOnUIThread,
this, results, extension_id));
}
void SpeechInputExtensionManager::SetRecognitionResultsOnUIThread(
const content::SpeechRecognitionResults& results,
const std::string& extension_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
content::SpeechRecognitionResults::const_iterator it = results.begin();
for (; it != results.end(); ++it) {
const content::SpeechRecognitionResult& result = (*it);
scoped_ptr<ListValue> args(new ListValue());
DictionaryValue* js_event = new DictionaryValue();
args->Append(js_event);
ListValue* js_hypothesis_array = new ListValue();
js_event->Set(kHypothesesKey, js_hypothesis_array);
for (size_t i = 0; i < result.hypotheses.size(); ++i) {
const SpeechRecognitionHypothesis& hypothesis = result.hypotheses[i];
DictionaryValue* js_hypothesis_object = new DictionaryValue();
js_hypothesis_array->Append(js_hypothesis_object);
js_hypothesis_object->SetString(kUtteranceKey,
UTF16ToUTF8(hypothesis.utterance));
js_hypothesis_object->SetDouble(kConfidenceKey,
hypothesis.confidence);
}
DispatchEventToExtension(extension_id, kOnResultEvent, args.Pass());
}
}
void SpeechInputExtensionManager::OnRecognitionStart(int session_id) {
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, speech_recognition_session_id_);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread,
this));
}
void SpeechInputExtensionManager::OnAudioEnd(int session_id) {
}
void SpeechInputExtensionManager::OnRecognitionEnd(int session_id) {
// 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() {
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;
DCHECK(profile_);
VLOG(1) << "Sending start notification";
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED,
content::Source<Profile>(profile_),
content::Details<std::string>(&extension_id_in_use_));
}
void SpeechInputExtensionManager::OnRecognitionError(
int session_id, const content::SpeechRecognitionError& error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(session_id, speech_recognition_session_id_);
VLOG(1) << "OnRecognitionError: " << error.code;
base::AutoLock auto_lock(state_lock_);
if (state_ == kShutdown)
return;
GetSpeechInputExtensionInterface()->StopRecording(true);
std::string event_error_code;
bool report_to_event = true;
switch (error.code) {
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;
report_to_event = false;
} else {
event_error_code = kErrorCaptureError;
}
break;
case content::SPEECH_RECOGNITION_ERROR_NETWORK:
event_error_code = kErrorNetworkError;
break;
case content::SPEECH_RECOGNITION_ERROR_BAD_GRAMMAR:
// 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 = kErrorUnableToStart;
break;
case content::SPEECH_RECOGNITION_ERROR_NO_SPEECH:
event_error_code = kErrorNoSpeechHeard;
break;
case content::SPEECH_RECOGNITION_ERROR_NO_MATCH:
event_error_code = kErrorNoResults;
break;
// The remaining kErrorAborted case should never be returned by the server.
default:
NOTREACHED();
}
if (!event_error_code.empty()) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::DispatchError,
this, event_error_code, report_to_event));
}
}
void SpeechInputExtensionManager::OnEnvironmentEstimationComplete(
int session_id) {
DCHECK_EQ(session_id, speech_recognition_session_id_);
}
void SpeechInputExtensionManager::OnSoundStart(int session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(session_id, speech_recognition_session_id_);
VLOG(1) << "OnSoundStart";
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension,
this, extension_id_in_use_, std::string(kOnSoundStartEvent),
Passed(scoped_ptr<ListValue>(new ListValue()))));
}
void SpeechInputExtensionManager::OnSoundEnd(int session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
VLOG(1) << "OnSoundEnd";
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension,
this, extension_id_in_use_, std::string(kOnSoundEndEvent),
Passed(scoped_ptr<ListValue>(new ListValue()))));
}
void SpeechInputExtensionManager::DispatchEventToExtension(
const std::string& extension_id, const std::string& event_name,
scoped_ptr<ListValue> event_args) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::AutoLock auto_lock(state_lock_);
if (state_ == kShutdown)
return;
if (profile_ && extensions::ExtensionSystem::Get(profile_)->event_router()) {
scoped_ptr<extensions::Event> event(new extensions::Event(
event_name, event_args.Pass()));
event->restrict_to_profile = profile_;
extensions::ExtensionSystem::Get(profile_)->event_router()->
DispatchEventToExtension(extension_id, event.Pass());
}
}
void SpeechInputExtensionManager::DispatchError(
const std::string& error, bool dispatch_event) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
std::string extension_id;
{
base::AutoLock auto_lock(state_lock_);
if (state_ == kShutdown)
return;
extension_id = extension_id_in_use_;
ResetToIdleState();
// Will set the error property in the ongoing extension function calls.
ExtensionError details(extension_id, error);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED,
content::Source<Profile>(profile_),
content::Details<ExtensionError>(&details));
}
// Used for errors that are also reported via the onError event.
if (dispatch_event) {
scoped_ptr<ListValue> args(new ListValue());
DictionaryValue* js_error = new DictionaryValue();
args->Append(js_error);
js_error->SetString(kErrorCodeKey, error);
DispatchEventToExtension(extension_id, kOnErrorEvent, args.Pass());
}
}
bool SpeechInputExtensionManager::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 = kErrorRequestDenied;
return false;
}
switch (state_) {
case kIdle:
break;
case kStarting:
*error = kErrorRequestInProgress;
return false;
case kRecording:
case kStopping:
*error = kErrorInvalidOperation;
return false;
default:
NOTREACHED();
}
const extensions::Extension* extension =
extensions::ExtensionSystem::Get(profile_)->extension_service()->
GetExtensionById(extension_id, true);
DCHECK(extension);
const std::string& extension_name = extension->name();
extension_id_in_use_ = extension_id;
VLOG(1) << "State changed to starting";
state_ = kStarting;
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter =
profile_->GetRequestContext();
const int render_process_id = GetRenderProcessIDForExtension(extension_id);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this,
url_request_context_getter, extension_name, language, grammar,
filter_profanities, render_process_id));
return true;
}
void SpeechInputExtensionManager::StartOnIOThread(
scoped_refptr<net::URLRequestContextGetter> context_getter,
const std::string& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities,
int render_process_id) {
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;
// 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.
if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::DispatchError, this,
std::string(kErrorNoRecordingDeviceFound), false));
return;
}
if (GetSpeechInputExtensionInterface()->IsCapturingAudio()) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::DispatchError, this,
std::string(kErrorRecordingDeviceInUse), false));
return;
}
GetSpeechInputExtensionInterface()->StartRecording(this,
context_getter,
extension_name,
language,
grammar,
filter_profanities,
render_process_id);
}
bool SpeechInputExtensionManager::HasAudioInputDevices() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
return SpeechRecognitionManager::GetInstance()->HasAudioInputDevices();
}
bool SpeechInputExtensionManager::IsCapturingAudio() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
return SpeechRecognitionManager::GetInstance()->IsCapturingAudio();
}
void SpeechInputExtensionManager::IsRecording(
const IsRecordingCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::IsRecordingOnIOThread,
this, callback));
}
void SpeechInputExtensionManager::IsRecordingOnIOThread(
const IsRecordingCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
bool result = GetSpeechInputExtensionInterface()->IsCapturingAudio();
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread,
this, callback, result));
}
void SpeechInputExtensionManager::IsRecordingOnUIThread(
const IsRecordingCallback& callback,
bool result) {
BrowserThread::CurrentlyOn(BrowserThread::UI);
callback.Run(result);
}
void SpeechInputExtensionManager::StartRecording(
content::SpeechRecognitionEventListener* listener,
net::URLRequestContextGetter* context_getter,
const std::string& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities,
int render_process_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
content::SpeechRecognitionSessionContext context;
context.requested_by_page_element = false;
context.render_process_id = render_process_id;
context.context_name = extension_name;
content::SpeechRecognitionSessionConfig config;
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() {
if (!is_recognition_in_progress_)
return false;
return SpeechRecognitionManager::GetInstance()->IsCapturingAudio();
}
bool SpeechInputExtensionManager::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 = kErrorRequestDenied;
return false;
}
switch (state_) {
case kRecording:
break;
case kStopping:
*error = kErrorRequestInProgress;
return false;
case kIdle:
case kStarting:
*error = kErrorInvalidOperation;
return false;
default:
NOTREACHED();
}
// Guarded by the state lock.
DCHECK(GetSpeechInputExtensionInterface()->HasValidRecognizer());
VLOG(1) << "State changed to stopping";
state_ = kStopping;
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this));
return true;
}
void SpeechInputExtensionManager::ForceStopOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
VLOG(1) << "Requesting forced stop (IO thread)";
base::AutoLock auto_lock(state_lock_);
DCHECK(state_ != kIdle);
GetSpeechInputExtensionInterface()->StopRecording(false);
if (state_ == kShutdown)
return;
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::StopSucceededOnUIThread, this));
}
void SpeechInputExtensionManager::StopRecording(bool recognition_failed) {
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() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(1) << "Stop succeeded (UI thread)";
base::AutoLock auto_lock(state_lock_);
if (state_ == kShutdown)
return;
std::string extension_id = extension_id_in_use_;
ResetToIdleState();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED,
// Guarded by the state_ == kShutdown check.
content::Source<Profile>(profile_),
content::Details<std::string>(&extension_id));
}
void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id,
float volume,
float noise_volume) {}
namespace extensions {
SpeechInputAPI::SpeechInputAPI(Profile* profile)
: manager_(new SpeechInputExtensionManager(profile)) {
ExtensionFunctionRegistry* registry =
ExtensionFunctionRegistry::GetInstance();
registry->RegisterFunction<StartSpeechInputFunction>();
registry->RegisterFunction<StopSpeechInputFunction>();
registry->RegisterFunction<IsRecordingSpeechInputFunction>();
}
SpeechInputAPI::~SpeechInputAPI() {
}
void SpeechInputAPI::Shutdown() {
manager_->ShutdownOnUIThread();
}
static base::LazyInstance<ProfileKeyedAPIFactory<SpeechInputAPI> >
g_factory = LAZY_INSTANCE_INITIALIZER;
// static
ProfileKeyedAPIFactory<SpeechInputAPI>* SpeechInputAPI::GetFactoryInstance() {
return &g_factory.Get();
}
} // namespace extensions