1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/speech/speech_input_extension_manager.h"
8 #include "base/json/json_writer.h"
9 #include "base/utf_string_conversions.h"
10 #include "base/values.h"
11 #include "chrome/browser/extensions/event_router.h"
12 #include "chrome/browser/extensions/extension_host.h"
13 #include "chrome/browser/extensions/extension_process_manager.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/extension_system.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/profiles/profile_dependency_manager.h"
18 #include "chrome/browser/profiles/profile_keyed_service.h"
19 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
20 #include "chrome/common/chrome_notification_types.h"
21 #include "chrome/common/extensions/extension.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/notification_registrar.h"
24 #include "content/public/browser/notification_service.h"
25 #include "content/public/browser/render_process_host.h"
26 #include "content/public/browser/speech_recognition_manager.h"
27 #include "content/public/browser/speech_recognition_session_config.h"
28 #include "content/public/browser/speech_recognition_session_context.h"
29 #include "content/public/common/speech_recognition_error.h"
30 #include "content/public/common/speech_recognition_result.h"
31 #include "net/url_request/url_request_context_getter.h"
33 using content::BrowserThread
;
34 using content::SpeechRecognitionHypothesis
;
35 using content::SpeechRecognitionManager
;
39 const char kErrorNoRecordingDeviceFound
[] = "noRecordingDeviceFound";
40 const char kErrorRecordingDeviceInUse
[] = "recordingDeviceInUse";
41 const char kErrorUnableToStart
[] = "unableToStart";
42 const char kErrorRequestDenied
[] = "requestDenied";
43 const char kErrorRequestInProgress
[] = "requestInProgress";
44 const char kErrorInvalidOperation
[] = "invalidOperation";
46 const char kErrorCodeKey
[] = "code";
47 const char kErrorCaptureError
[] = "captureError";
48 const char kErrorNetworkError
[] = "networkError";
49 const char kErrorNoSpeechHeard
[] = "noSpeechHeard";
50 const char kErrorNoResults
[] = "noResults";
52 const char kUtteranceKey
[] = "utterance";
53 const char kConfidenceKey
[] = "confidence";
54 const char kHypothesesKey
[] = "hypotheses";
56 const char kOnErrorEvent
[] = "experimental.speechInput.onError";
57 const char kOnResultEvent
[] = "experimental.speechInput.onResult";
58 const char kOnSoundStartEvent
[] = "experimental.speechInput.onSoundStart";
59 const char kOnSoundEndEvent
[] = "experimental.speechInput.onSoundEnd";
61 // Wrap an SpeechInputExtensionManager using scoped_refptr to avoid
62 // assertion failures on destruction because of not using release().
63 class SpeechInputExtensionManagerWrapper
: public ProfileKeyedService
{
65 explicit SpeechInputExtensionManagerWrapper(
66 SpeechInputExtensionManager
* manager
)
67 : manager_(manager
) {}
69 virtual ~SpeechInputExtensionManagerWrapper() {}
71 SpeechInputExtensionManager
* manager() const { return manager_
.get(); }
74 // Methods from ProfileKeyedService.
75 virtual void Shutdown() OVERRIDE
{
76 manager()->ShutdownOnUIThread();
79 scoped_refptr
<SpeechInputExtensionManager
> manager_
;
83 // Factory for SpeechInputExtensionManagers as profile keyed services.
84 class SpeechInputExtensionManager::Factory
: public ProfileKeyedServiceFactory
{
86 static void Initialize();
87 static Factory
* GetInstance();
89 SpeechInputExtensionManagerWrapper
* GetForProfile(Profile
* profile
);
92 friend struct DefaultSingletonTraits
<Factory
>;
97 // ProfileKeyedServiceFactory methods:
98 virtual ProfileKeyedService
* BuildServiceInstanceFor(
99 Profile
* profile
) const OVERRIDE
;
100 virtual bool ServiceRedirectedInIncognito() const OVERRIDE
{ return false; }
101 virtual bool ServiceIsNULLWhileTesting() const OVERRIDE
{ return true; }
102 virtual bool ServiceIsCreatedWithProfile() const OVERRIDE
{ return true; }
104 DISALLOW_COPY_AND_ASSIGN(Factory
);
107 void SpeechInputExtensionManager::Factory::Initialize() {
111 SpeechInputExtensionManager::Factory
*
112 SpeechInputExtensionManager::Factory::GetInstance() {
113 return Singleton
<SpeechInputExtensionManager::Factory
>::get();
116 SpeechInputExtensionManagerWrapper
*
117 SpeechInputExtensionManager::Factory::GetForProfile(
119 return static_cast<SpeechInputExtensionManagerWrapper
*>(
120 GetServiceForProfile(profile
, true));
123 SpeechInputExtensionManager::Factory::Factory()
124 : ProfileKeyedServiceFactory("SpeechInputExtensionManager",
125 ProfileDependencyManager::GetInstance()) {
128 SpeechInputExtensionManager::Factory::~Factory() {
132 SpeechInputExtensionManager::Factory::BuildServiceInstanceFor(
133 Profile
* profile
) const {
134 scoped_refptr
<SpeechInputExtensionManager
> manager(
135 new SpeechInputExtensionManager(profile
));
136 return new SpeechInputExtensionManagerWrapper(manager
);
139 SpeechInputExtensionInterface::SpeechInputExtensionInterface() {
142 SpeechInputExtensionInterface::~SpeechInputExtensionInterface() {
145 SpeechInputExtensionManager::SpeechInputExtensionManager(Profile
* profile
)
148 registrar_(new content::NotificationRegistrar
),
149 speech_interface_(NULL
),
150 is_recognition_in_progress_(false),
151 speech_recognition_session_id_(
152 SpeechRecognitionManager::kSessionIDInvalid
) {
153 registrar_
->Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED
,
154 content::Source
<Profile
>(profile_
));
157 SpeechInputExtensionManager::~SpeechInputExtensionManager() {
160 SpeechInputExtensionManager
* SpeechInputExtensionManager::GetForProfile(
162 SpeechInputExtensionManagerWrapper
* wrapper
=
163 Factory::GetInstance()->GetForProfile(profile
);
166 return wrapper
->manager();
169 void SpeechInputExtensionManager::InitializeFactory() {
170 Factory::Initialize();
173 void SpeechInputExtensionManager::Observe(int type
,
174 const content::NotificationSource
& source
,
175 const content::NotificationDetails
& details
) {
176 if (type
== chrome::NOTIFICATION_EXTENSION_UNLOADED
) {
178 content::Details
<extensions::UnloadedExtensionInfo
>(details
)->
185 void SpeechInputExtensionManager::ShutdownOnUIThread() {
186 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
187 VLOG(1) << "Profile shutting down.";
189 // Note: Unretained(this) is safe, also if we are freed in the meanwhile.
190 // It is used by the SR manager just for comparing the raw pointer and remove
191 // the associated sessions.
192 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
193 base::Bind(&SpeechInputExtensionManager::AbortAllSessionsOnIOThread
,
194 base::Unretained(this)));
196 base::AutoLock
auto_lock(state_lock_
);
197 DCHECK(state_
!= kShutdown
);
198 if (state_
!= kIdle
) {
199 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
200 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread
, this));
203 VLOG(1) << "Entering the shutdown sink state.";
208 void SpeechInputExtensionManager::AbortAllSessionsOnIOThread() {
209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
210 // TODO(primiano): The following check should not be really needed if the
211 // SpeechRecognitionManager and this class are destroyed in the correct order
212 // (this class first), as it is in current chrome implementation.
213 // However, it seems the some ChromiumOS tests violate the destruction order
214 // envisaged by browser_main_loop, so SpeechRecognitionmanager could have been
216 if (SpeechRecognitionManager
* mgr
= SpeechRecognitionManager::GetInstance())
217 mgr
->AbortAllSessionsForListener(this);
220 void SpeechInputExtensionManager::ExtensionUnloaded(
221 const std::string
& extension_id
) {
222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
224 base::AutoLock
auto_lock(state_lock_
);
225 if (state_
== kShutdown
)
228 VLOG(1) << "Extension unloaded. Requesting to enforce stop...";
229 if (extension_id_in_use_
== extension_id
) {
230 if (state_
!= kIdle
) {
231 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
232 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread
, this));
237 void SpeechInputExtensionManager::SetSpeechInputExtensionInterface(
238 SpeechInputExtensionInterface
* speech_interface
) {
239 speech_interface_
= speech_interface
;
242 SpeechInputExtensionInterface
*
243 SpeechInputExtensionManager::GetSpeechInputExtensionInterface() {
244 return speech_interface_
? speech_interface_
: this;
247 void SpeechInputExtensionManager::ResetToIdleState() {
248 VLOG(1) << "State changed to idle. Deassociating any extensions.";
250 extension_id_in_use_
.clear();
253 int SpeechInputExtensionManager::GetRenderProcessIDForExtension(
254 const std::string
& extension_id
) const {
255 ExtensionProcessManager
* epm
=
256 extensions::ExtensionSystem::Get(profile_
)->process_manager();
258 extensions::ExtensionHost
* eh
=
259 epm
->GetBackgroundHostForExtension(extension_id
);
261 content::RenderProcessHost
* rph
= eh
->render_process_host();
266 void SpeechInputExtensionManager::OnRecognitionResult(
268 const content::SpeechRecognitionResult
& result
) {
269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
270 DCHECK_EQ(session_id
, speech_recognition_session_id_
);
272 // Stopping will start the disassociation with the extension.
273 // Make a copy to report the results to the proper one.
274 std::string extension_id
= extension_id_in_use_
;
275 ForceStopOnIOThread();
277 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
278 base::Bind(&SpeechInputExtensionManager::SetRecognitionResultOnUIThread
,
279 this, result
, extension_id
));
282 void SpeechInputExtensionManager::SetRecognitionResultOnUIThread(
283 const content::SpeechRecognitionResult
& result
,
284 const std::string
& extension_id
) {
285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
287 scoped_ptr
<ListValue
> args(new ListValue());
288 DictionaryValue
* js_event
= new DictionaryValue();
289 args
->Append(js_event
);
291 ListValue
* js_hypothesis_array
= new ListValue();
292 js_event
->Set(kHypothesesKey
, js_hypothesis_array
);
294 for (size_t i
= 0; i
< result
.hypotheses
.size(); ++i
) {
295 const SpeechRecognitionHypothesis
& hypothesis
= result
.hypotheses
[i
];
297 DictionaryValue
* js_hypothesis_object
= new DictionaryValue();
298 js_hypothesis_array
->Append(js_hypothesis_object
);
300 js_hypothesis_object
->SetString(kUtteranceKey
,
301 UTF16ToUTF8(hypothesis
.utterance
));
302 js_hypothesis_object
->SetDouble(kConfidenceKey
,
303 hypothesis
.confidence
);
306 DispatchEventToExtension(extension_id
, kOnResultEvent
, args
.Pass());
309 void SpeechInputExtensionManager::OnRecognitionStart(int session_id
) {
310 DCHECK_EQ(session_id
, speech_recognition_session_id_
);
313 void SpeechInputExtensionManager::OnAudioStart(int session_id
) {
314 VLOG(1) << "OnAudioStart";
315 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
316 DCHECK_EQ(session_id
, speech_recognition_session_id_
);
318 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
319 base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread
,
323 void SpeechInputExtensionManager::OnAudioEnd(int session_id
) {
326 void SpeechInputExtensionManager::OnRecognitionEnd(int session_id
) {
327 // In the very exceptional case in which we requested a new recognition before
328 // the previous one ended, don't clobber the speech_recognition_session_id_.
329 if (speech_recognition_session_id_
== session_id
) {
330 is_recognition_in_progress_
= false;
331 speech_recognition_session_id_
=
332 SpeechRecognitionManager::kSessionIDInvalid
;
336 void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() {
337 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
339 base::AutoLock
auto_lock(state_lock_
);
340 if (state_
== kShutdown
)
343 DCHECK_EQ(state_
, kStarting
);
344 VLOG(1) << "State changed to recording";
349 VLOG(1) << "Sending start notification";
350 content::NotificationService::current()->Notify(
351 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED
,
352 content::Source
<Profile
>(profile_
),
353 content::Details
<std::string
>(&extension_id_in_use_
));
356 void SpeechInputExtensionManager::OnRecognitionError(
357 int session_id
, const content::SpeechRecognitionError
& error
) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
359 DCHECK_EQ(session_id
, speech_recognition_session_id_
);
360 VLOG(1) << "OnRecognitionError: " << error
.code
;
362 base::AutoLock
auto_lock(state_lock_
);
363 if (state_
== kShutdown
)
366 GetSpeechInputExtensionInterface()->StopRecording(true);
368 std::string event_error_code
;
369 bool report_to_event
= true;
371 switch (error
.code
) {
372 case content::SPEECH_RECOGNITION_ERROR_NONE
:
375 case content::SPEECH_RECOGNITION_ERROR_ABORTED
:
376 // ERROR_ABORTED is received whenever AbortSession is called on the
377 // manager. However, we want propagate the error only if it is triggered
378 // by an external cause (another recognition started, aborting us), thus
379 // only if it occurs while we are capturing audio.
380 if (state_
== kRecording
)
381 event_error_code
= kErrorCaptureError
;
384 case content::SPEECH_RECOGNITION_ERROR_AUDIO
:
385 if (state_
== kStarting
) {
386 event_error_code
= kErrorUnableToStart
;
387 report_to_event
= false;
389 event_error_code
= kErrorCaptureError
;
393 case content::SPEECH_RECOGNITION_ERROR_NETWORK
:
394 event_error_code
= kErrorNetworkError
;
397 case content::SPEECH_RECOGNITION_ERROR_BAD_GRAMMAR
:
398 // No error is returned on invalid language, for example.
399 // To avoid confusion about when this is would be fired, the invalid
400 // params error is not being exposed to the onError event.
401 event_error_code
= kErrorUnableToStart
;
404 case content::SPEECH_RECOGNITION_ERROR_NO_SPEECH
:
405 event_error_code
= kErrorNoSpeechHeard
;
408 case content::SPEECH_RECOGNITION_ERROR_NO_MATCH
:
409 event_error_code
= kErrorNoResults
;
412 // The remaining kErrorAborted case should never be returned by the server.
417 if (!event_error_code
.empty()) {
418 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
419 base::Bind(&SpeechInputExtensionManager::DispatchError
,
420 this, event_error_code
, report_to_event
));
424 void SpeechInputExtensionManager::OnEnvironmentEstimationComplete(
426 DCHECK_EQ(session_id
, speech_recognition_session_id_
);
429 void SpeechInputExtensionManager::OnSoundStart(int session_id
) {
430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
431 DCHECK_EQ(session_id
, speech_recognition_session_id_
);
432 VLOG(1) << "OnSoundStart";
434 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
435 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension
,
436 this, extension_id_in_use_
, std::string(kOnSoundStartEvent
),
437 Passed(scoped_ptr
<ListValue
>(new ListValue()))));
440 void SpeechInputExtensionManager::OnSoundEnd(int session_id
) {
441 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
442 VLOG(1) << "OnSoundEnd";
444 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
445 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension
,
446 this, extension_id_in_use_
, std::string(kOnSoundEndEvent
),
447 Passed(scoped_ptr
<ListValue
>(new ListValue()))));
450 void SpeechInputExtensionManager::DispatchEventToExtension(
451 const std::string
& extension_id
, const std::string
& event
,
452 scoped_ptr
<ListValue
> event_args
) {
453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
455 base::AutoLock
auto_lock(state_lock_
);
456 if (state_
== kShutdown
)
459 if (profile_
&& extensions::ExtensionSystem::Get(profile_
)->event_router()) {
460 extensions::ExtensionSystem::Get(profile_
)->event_router()->
461 DispatchEventToExtension(extension_id
, event
, event_args
.Pass(),
466 void SpeechInputExtensionManager::DispatchError(
467 const std::string
& error
, bool dispatch_event
) {
468 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
470 std::string extension_id
;
472 base::AutoLock
auto_lock(state_lock_
);
473 if (state_
== kShutdown
)
476 extension_id
= extension_id_in_use_
;
479 // Will set the error property in the ongoing extension function calls.
480 ExtensionError
details(extension_id
, error
);
481 content::NotificationService::current()->Notify(
482 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED
,
483 content::Source
<Profile
>(profile_
),
484 content::Details
<ExtensionError
>(&details
));
487 // Used for errors that are also reported via the onError event.
488 if (dispatch_event
) {
489 scoped_ptr
<ListValue
> args(new ListValue());
490 DictionaryValue
* js_error
= new DictionaryValue();
491 args
->Append(js_error
);
492 js_error
->SetString(kErrorCodeKey
, error
);
493 DispatchEventToExtension(extension_id
, kOnErrorEvent
, args
.Pass());
497 bool SpeechInputExtensionManager::Start(
498 const std::string
& extension_id
, const std::string
& language
,
499 const std::string
& grammar
, bool filter_profanities
, std::string
* error
) {
500 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
502 VLOG(1) << "Requesting start (UI thread)";
504 base::AutoLock
auto_lock(state_lock_
);
505 if (state_
== kShutdown
||
506 (!extension_id_in_use_
.empty() && extension_id_in_use_
!= extension_id
)) {
507 *error
= kErrorRequestDenied
;
516 *error
= kErrorRequestInProgress
;
521 *error
= kErrorInvalidOperation
;
528 const extensions::Extension
* extension
= profile_
->GetExtensionService()->
529 GetExtensionById(extension_id
, true);
531 const std::string
& extension_name
= extension
->name();
533 extension_id_in_use_
= extension_id
;
534 VLOG(1) << "State changed to starting";
537 scoped_refptr
<net::URLRequestContextGetter
> url_request_context_getter
=
538 profile_
->GetRequestContext();
540 const int render_process_id
= GetRenderProcessIDForExtension(extension_id
);
542 BrowserThread::PostTask(
543 BrowserThread::IO
, FROM_HERE
,
544 base::Bind(&SpeechInputExtensionManager::StartOnIOThread
, this,
545 url_request_context_getter
, extension_name
, language
, grammar
,
546 filter_profanities
, render_process_id
));
550 void SpeechInputExtensionManager::StartOnIOThread(
551 scoped_refptr
<net::URLRequestContextGetter
> context_getter
,
552 const std::string
& extension_name
,
553 const std::string
& language
,
554 const std::string
& grammar
,
555 bool filter_profanities
,
556 int render_process_id
) {
557 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
558 VLOG(1) << "Requesting start (IO thread)";
560 // Everything put inside the lock to ensure the validity of context_getter,
561 // guaranteed while not in the shutdown state. Any ongoing or recognition
562 // request will be requested to be aborted when entering the shutdown state.
563 base::AutoLock
auto_lock(state_lock_
);
564 if (state_
== kShutdown
)
567 // TODO(primiano): These two checks below could be avoided, since they are
568 // already handled in the speech recognition classes. However, since the
569 // speech input extensions tests are bypassing the manager, we need them to
571 if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) {
572 BrowserThread::PostTask(
573 BrowserThread::UI
, FROM_HERE
,
574 base::Bind(&SpeechInputExtensionManager::DispatchError
, this,
575 std::string(kErrorNoRecordingDeviceFound
), false));
579 if (GetSpeechInputExtensionInterface()->IsCapturingAudio()) {
580 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
581 base::Bind(&SpeechInputExtensionManager::DispatchError
, this,
582 std::string(kErrorRecordingDeviceInUse
), false));
586 GetSpeechInputExtensionInterface()->StartRecording(this,
595 bool SpeechInputExtensionManager::HasAudioInputDevices() {
596 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
597 return SpeechRecognitionManager::GetInstance()->HasAudioInputDevices();
600 bool SpeechInputExtensionManager::IsCapturingAudio() {
601 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
602 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio();
605 void SpeechInputExtensionManager::IsRecording(
606 const IsRecordingCallback
& callback
) {
607 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
608 BrowserThread::PostTask(
609 BrowserThread::IO
, FROM_HERE
,
610 base::Bind(&SpeechInputExtensionManager::IsRecordingOnIOThread
,
614 void SpeechInputExtensionManager::IsRecordingOnIOThread(
615 const IsRecordingCallback
& callback
) {
616 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
618 bool result
= GetSpeechInputExtensionInterface()->IsCapturingAudio();
620 BrowserThread::PostTask(
621 BrowserThread::UI
, FROM_HERE
,
622 base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread
,
623 this, callback
, result
));
626 void SpeechInputExtensionManager::IsRecordingOnUIThread(
627 const IsRecordingCallback
& callback
,
629 BrowserThread::CurrentlyOn(BrowserThread::UI
);
630 callback
.Run(result
);
633 void SpeechInputExtensionManager::StartRecording(
634 content::SpeechRecognitionEventListener
* listener
,
635 net::URLRequestContextGetter
* context_getter
,
636 const std::string
& extension_name
,
637 const std::string
& language
,
638 const std::string
& grammar
,
639 bool filter_profanities
,
640 int render_process_id
) {
641 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
643 content::SpeechRecognitionSessionContext context
;
644 context
.requested_by_page_element
= false;
645 context
.render_process_id
= render_process_id
;
646 context
.context_name
= extension_name
;
648 content::SpeechRecognitionSessionConfig config
;
649 config
.language
= language
;
650 config
.grammars
.push_back(content::SpeechRecognitionGrammar(grammar
));
651 config
.initial_context
= context
;
652 config
.url_request_context_getter
= context_getter
;
653 config
.filter_profanities
= filter_profanities
;
654 config
.event_listener
= listener
;
656 DCHECK(!is_recognition_in_progress_
);
657 SpeechRecognitionManager
& manager
= *SpeechRecognitionManager::GetInstance();
658 speech_recognition_session_id_
=
659 manager
.CreateSession(config
);
660 DCHECK_NE(speech_recognition_session_id_
,
661 SpeechRecognitionManager::kSessionIDInvalid
);
662 is_recognition_in_progress_
= true;
663 manager
.StartSession(speech_recognition_session_id_
);
666 bool SpeechInputExtensionManager::HasValidRecognizer() {
667 if (!is_recognition_in_progress_
)
669 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio();
672 bool SpeechInputExtensionManager::Stop(const std::string
& extension_id
,
673 std::string
* error
) {
674 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
676 VLOG(1) << "Requesting stop (UI thread)";
678 base::AutoLock
auto_lock(state_lock_
);
679 if (state_
== kShutdown
||
680 (!extension_id_in_use_
.empty() && extension_id_in_use_
!= extension_id
)) {
681 *error
= kErrorRequestDenied
;
690 *error
= kErrorRequestInProgress
;
695 *error
= kErrorInvalidOperation
;
702 // Guarded by the state lock.
703 DCHECK(GetSpeechInputExtensionInterface()->HasValidRecognizer());
705 VLOG(1) << "State changed to stopping";
708 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
709 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread
, this));
713 void SpeechInputExtensionManager::ForceStopOnIOThread() {
714 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
715 VLOG(1) << "Requesting forced stop (IO thread)";
717 base::AutoLock
auto_lock(state_lock_
);
718 DCHECK(state_
!= kIdle
);
720 GetSpeechInputExtensionInterface()->StopRecording(false);
722 if (state_
== kShutdown
)
725 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
726 base::Bind(&SpeechInputExtensionManager::StopSucceededOnUIThread
, this));
729 void SpeechInputExtensionManager::StopRecording(bool recognition_failed
) {
730 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
731 if (!is_recognition_in_progress_
)
733 DCHECK_NE(speech_recognition_session_id_
,
734 SpeechRecognitionManager::kSessionIDInvalid
);
735 SpeechRecognitionManager::GetInstance()->AbortSession(
736 speech_recognition_session_id_
);
739 void SpeechInputExtensionManager::StopSucceededOnUIThread() {
740 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
741 VLOG(1) << "Stop succeeded (UI thread)";
743 base::AutoLock
auto_lock(state_lock_
);
744 if (state_
== kShutdown
)
747 std::string extension_id
= extension_id_in_use_
;
750 content::NotificationService::current()->Notify(
751 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED
,
752 // Guarded by the state_ == kShutdown check.
753 content::Source
<Profile
>(profile_
),
754 content::Details
<std::string
>(&extension_id
));
757 void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id
,
759 float noise_volume
) {}