OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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. | |
4 | |
5 #include "chrome/browser/speech/speech_input_extension_manager.h" | |
6 | |
7 #include "base/bind.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/extension_event_router.h" | |
12 #include "chrome/browser/extensions/extension_service.h" | |
13 #include "chrome/browser/prefs/pref_service.h" | |
14 #include "chrome/browser/profiles/profile.h" | |
15 #include "chrome/browser/profiles/profile_dependency_manager.h" | |
16 #include "chrome/browser/profiles/profile_keyed_service.h" | |
17 #include "chrome/browser/profiles/profile_keyed_service_factory.h" | |
18 #include "chrome/common/chrome_notification_types.h" | |
19 #include "chrome/common/extensions/extension.h" | |
20 #include "chrome/common/pref_names.h" | |
21 #include "content/public/browser/browser_thread.h" | |
22 #include "content/public/browser/notification_service.h" | |
23 | |
24 using content::BrowserThread; | |
25 using namespace speech_input; | |
26 | |
27 namespace { | |
28 | |
29 const char kErrorNoRecordingDeviceFound[] = "noRecordingDeviceFound"; | |
30 const char kErrorRecordingDeviceInUse[] = "recordingDeviceInUse"; | |
31 const char kErrorUnableToStart[] = "unableToStart"; | |
32 const char kErrorRequestDenied[] = "requestDenied"; | |
33 const char kErrorRequestInProgress[] = "requestInProgress"; | |
34 const char kErrorInvalidOperation[] = "invalidOperation"; | |
35 | |
36 const char kErrorCodeKey[] = "code"; | |
37 const char kErrorCaptureError[] = "captureError"; | |
38 const char kErrorNetworkError[] = "networkError"; | |
39 const char kErrorNoSpeechHeard[] = "noSpeechHeard"; | |
40 const char kErrorNoResults[] = "noResults"; | |
41 | |
42 const char kUtteranceKey[] = "utterance"; | |
43 const char kConfidenceKey[] = "confidence"; | |
44 const char kHypothesesKey[] = "hypotheses"; | |
45 | |
46 const char kOnErrorEvent[] = "experimental.speechInput.onError"; | |
47 const char kOnResultEvent[] = "experimental.speechInput.onResult"; | |
48 const char kOnSoundStartEvent[] = "experimental.speechInput.onSoundStart"; | |
49 const char kOnSoundEndEvent[] = "experimental.speechInput.onSoundEnd"; | |
50 | |
51 // Caller id provided to the speech recognizer. Since only one extension can | |
52 // be recording on the same time a constant value is enough as id. | |
53 static const int kSpeechCallerId = 1; | |
54 | |
55 // Wrap an SpeechInputExtensionManager using scoped_refptr to avoid | |
56 // assertion failures on destruction because of not using release(). | |
57 class SpeechInputExtensionManagerWrapper : public ProfileKeyedService { | |
58 public: | |
59 explicit SpeechInputExtensionManagerWrapper( | |
60 SpeechInputExtensionManager* manager) | |
61 : manager_(manager) {} | |
62 | |
63 virtual ~SpeechInputExtensionManagerWrapper() {} | |
64 | |
65 SpeechInputExtensionManager* manager() const { return manager_.get(); } | |
66 | |
67 private: | |
68 // Methods from ProfileKeyedService. | |
69 virtual void Shutdown() OVERRIDE { | |
70 manager()->ShutdownOnUIThread(); | |
71 } | |
72 | |
73 scoped_refptr<SpeechInputExtensionManager> manager_; | |
74 }; | |
75 | |
76 } | |
77 | |
78 // Factory for SpeechInputExtensionManagers as profile keyed services. | |
79 class SpeechInputExtensionManager::Factory : public ProfileKeyedServiceFactory { | |
80 public: | |
81 static void Initialize(); | |
82 static Factory* GetInstance(); | |
83 | |
84 SpeechInputExtensionManagerWrapper* GetForProfile(Profile* profile); | |
85 | |
86 private: | |
87 friend struct DefaultSingletonTraits<Factory>; | |
88 | |
89 Factory(); | |
90 virtual ~Factory(); | |
91 | |
92 // ProfileKeyedServiceFactory methods: | |
93 virtual ProfileKeyedService* BuildServiceInstanceFor( | |
94 Profile* profile) const OVERRIDE; | |
95 virtual bool ServiceRedirectedInIncognito() OVERRIDE { return false; } | |
96 virtual bool ServiceIsNULLWhileTesting() OVERRIDE { return true; } | |
97 virtual bool ServiceIsCreatedWithProfile() OVERRIDE { return true; } | |
98 | |
99 DISALLOW_COPY_AND_ASSIGN(Factory); | |
100 }; | |
101 | |
102 void SpeechInputExtensionManager::Factory::Initialize() { | |
103 GetInstance(); | |
104 } | |
105 | |
106 SpeechInputExtensionManager::Factory* | |
107 SpeechInputExtensionManager::Factory::GetInstance() { | |
108 return Singleton<SpeechInputExtensionManager::Factory>::get(); | |
109 } | |
110 | |
111 SpeechInputExtensionManagerWrapper* | |
112 SpeechInputExtensionManager::Factory::GetForProfile( | |
113 Profile* profile) { | |
114 return static_cast<SpeechInputExtensionManagerWrapper*>( | |
115 GetServiceForProfile(profile, true)); | |
116 } | |
117 | |
118 SpeechInputExtensionManager::Factory::Factory() | |
119 : ProfileKeyedServiceFactory(ProfileDependencyManager::GetInstance()) { | |
120 } | |
121 | |
122 SpeechInputExtensionManager::Factory::~Factory() { | |
123 } | |
124 | |
125 ProfileKeyedService* | |
126 SpeechInputExtensionManager::Factory::BuildServiceInstanceFor( | |
127 Profile* profile) const { | |
128 scoped_refptr<SpeechInputExtensionManager> manager( | |
129 new SpeechInputExtensionManager(profile)); | |
130 return new SpeechInputExtensionManagerWrapper(manager); | |
131 } | |
132 | |
133 SpeechInputExtensionInterface::SpeechInputExtensionInterface() { | |
134 } | |
135 | |
136 SpeechInputExtensionInterface::~SpeechInputExtensionInterface() { | |
137 } | |
138 | |
139 SpeechInputExtensionManager::SpeechInputExtensionManager(Profile* profile) | |
140 : profile_(profile), | |
141 state_(kIdle), | |
142 speech_interface_(NULL), | |
143 notification_(profile) { | |
144 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, | |
145 content::Source<Profile>(profile_)); | |
146 } | |
147 | |
148 SpeechInputExtensionManager::~SpeechInputExtensionManager() { | |
149 } | |
150 | |
151 SpeechInputExtensionManager* SpeechInputExtensionManager::GetForProfile( | |
152 Profile* profile) { | |
153 SpeechInputExtensionManagerWrapper *wrapper = | |
154 Factory::GetInstance()->GetForProfile(profile); | |
155 if (!wrapper) | |
156 return NULL; | |
157 return wrapper->manager(); | |
158 } | |
159 | |
160 void SpeechInputExtensionManager::InitializeFactory() { | |
161 Factory::Initialize(); | |
162 } | |
163 | |
164 void SpeechInputExtensionManager::Observe(int type, | |
165 const content::NotificationSource& source, | |
166 const content::NotificationDetails& details) { | |
167 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) { | |
168 ExtensionUnloaded( | |
169 content::Details<UnloadedExtensionInfo>(details)->extension->id()); | |
170 } else { | |
171 NOTREACHED(); | |
172 } | |
173 } | |
174 | |
175 void SpeechInputExtensionManager::ShutdownOnUIThread() { | |
176 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
177 VLOG(1) << "Profile shutting down."; | |
178 | |
179 base::AutoLock auto_lock(state_lock_); | |
180 DCHECK(state_ != kShutdown); | |
181 if (state_ != kIdle) { | |
182 notification_.Hide(); | |
183 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
184 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); | |
185 } | |
186 state_ = kShutdown; | |
187 VLOG(1) << "Entering the shutdown sink state."; | |
188 registrar_.RemoveAll(); | |
189 profile_ = NULL; | |
190 } | |
191 | |
192 void SpeechInputExtensionManager::ExtensionUnloaded( | |
193 const std::string& extension_id) { | |
194 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
195 | |
196 base::AutoLock auto_lock(state_lock_); | |
197 if (state_ == kShutdown) | |
198 return; | |
199 | |
200 VLOG(1) << "Extension unloaded. Requesting to enforce stop..."; | |
201 if (extension_id_in_use_ == extension_id) { | |
202 if (state_ != kIdle) { | |
203 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
204 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); | |
205 } | |
206 } | |
207 } | |
208 | |
209 void SpeechInputExtensionManager::SetSpeechInputExtensionInterface( | |
210 SpeechInputExtensionInterface* interface) { | |
211 speech_interface_ = interface; | |
212 } | |
213 | |
214 SpeechInputExtensionInterface* | |
215 SpeechInputExtensionManager::GetSpeechInputExtensionInterface() { | |
216 return speech_interface_ ? speech_interface_ : this; | |
217 } | |
218 | |
219 void SpeechInputExtensionManager::ResetToIdleState() { | |
220 VLOG(1) << "State changed to idle. Deassociating any extensions."; | |
221 state_ = kIdle; | |
222 extension_id_in_use_.clear(); | |
223 } | |
224 | |
225 void SpeechInputExtensionManager::SetRecognitionResult( | |
226 int caller_id, | |
227 const SpeechInputResult& result) { | |
228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
229 DCHECK_EQ(caller_id, kSpeechCallerId); | |
230 | |
231 // Stopping will start the disassociation with the extension. | |
232 // Make a copy to report the results to the proper one. | |
233 std::string extension_id = extension_id_in_use_; | |
234 ForceStopOnIOThread(); | |
235 | |
236 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
237 base::Bind(&SpeechInputExtensionManager::SetRecognitionResultOnUIThread, | |
238 this, result, extension_id)); | |
239 } | |
240 | |
241 void SpeechInputExtensionManager::SetRecognitionResultOnUIThread( | |
242 const SpeechInputResult& result, const std::string& extension_id) { | |
243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
244 | |
245 ListValue args; | |
246 DictionaryValue* js_event = new DictionaryValue(); | |
247 args.Append(js_event); | |
248 | |
249 ListValue* js_hypothesis_array = new ListValue(); | |
250 js_event->Set(kHypothesesKey, js_hypothesis_array); | |
251 | |
252 for (size_t i = 0; i < result.hypotheses.size(); ++i) { | |
253 const SpeechInputHypothesis& hypothesis = result.hypotheses[i]; | |
254 | |
255 DictionaryValue* js_hypothesis_object = new DictionaryValue(); | |
256 js_hypothesis_array->Append(js_hypothesis_object); | |
257 | |
258 js_hypothesis_object->SetString(kUtteranceKey, | |
259 UTF16ToUTF8(hypothesis.utterance)); | |
260 js_hypothesis_object->SetDouble(kConfidenceKey, | |
261 hypothesis.confidence); | |
262 } | |
263 | |
264 std::string json_args; | |
265 base::JSONWriter::Write(&args, false, &json_args); | |
266 VLOG(1) << "Results: " << json_args; | |
267 DispatchEventToExtension(extension_id, kOnResultEvent, json_args); | |
268 } | |
269 | |
270 void SpeechInputExtensionManager::DidStartReceivingAudio(int caller_id) { | |
271 VLOG(1) << "DidStartReceivingAudio"; | |
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
273 DCHECK_EQ(caller_id, kSpeechCallerId); | |
274 | |
275 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
276 base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread, | |
277 this)); | |
278 } | |
279 | |
280 void SpeechInputExtensionManager::DidCompleteRecording(int caller_id) { | |
281 DCHECK_EQ(caller_id, kSpeechCallerId); | |
282 } | |
283 | |
284 void SpeechInputExtensionManager::DidCompleteRecognition(int caller_id) { | |
285 DCHECK_EQ(caller_id, kSpeechCallerId); | |
286 } | |
287 | |
288 void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { | |
289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
290 | |
291 base::AutoLock auto_lock(state_lock_); | |
292 if (state_ == kShutdown) | |
293 return; | |
294 | |
295 DCHECK_EQ(state_, kStarting); | |
296 VLOG(1) << "State changed to recording"; | |
297 state_ = kRecording; | |
298 | |
299 const Extension* extension = profile_->GetExtensionService()-> | |
300 GetExtensionById(extension_id_in_use_, true); | |
301 DCHECK(extension); | |
302 | |
303 bool show_notification = !profile_->GetPrefs()->GetBoolean( | |
304 prefs::kSpeechInputTrayNotificationShown); | |
305 | |
306 notification_.Show(extension, show_notification); | |
307 | |
308 if (show_notification) { | |
309 profile_->GetPrefs()->SetBoolean( | |
310 prefs::kSpeechInputTrayNotificationShown, true); | |
311 } | |
312 | |
313 VLOG(1) << "Sending start notification"; | |
314 content::NotificationService::current()->Notify( | |
315 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED, | |
316 content::Source<Profile>(profile_), | |
317 content::Details<std::string>(&extension_id_in_use_)); | |
318 } | |
319 | |
320 void SpeechInputExtensionManager::OnRecognizerError( | |
321 int caller_id, SpeechInputError error) { | |
322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
323 DCHECK_EQ(caller_id, kSpeechCallerId); | |
324 VLOG(1) << "OnRecognizerError: " << error; | |
325 | |
326 base::AutoLock auto_lock(state_lock_); | |
327 if (state_ == kShutdown) | |
328 return; | |
329 | |
330 // Release the recognizer object. | |
331 GetSpeechInputExtensionInterface()->StopRecording(true); | |
332 | |
333 std::string event_error_code; | |
334 bool report_to_event = true; | |
335 | |
336 switch (error) { | |
337 case kErrorNone: | |
338 break; | |
339 | |
340 case kErrorAudio: | |
341 if (state_ == kStarting) { | |
342 event_error_code = kErrorUnableToStart; | |
343 report_to_event = false; | |
344 } else { | |
345 event_error_code = kErrorCaptureError; | |
346 } | |
347 break; | |
348 | |
349 case kErrorNetwork: | |
350 event_error_code = kErrorNetworkError; | |
351 break; | |
352 | |
353 case kErrorBadGrammar: | |
354 // No error is returned on invalid language, for example. | |
355 // To avoid confusion about when this is would be fired, the invalid | |
356 // params error is not being exposed to the onError event. | |
357 event_error_code = kErrorUnableToStart; | |
358 break; | |
359 | |
360 case kErrorNoSpeech: | |
361 event_error_code = kErrorNoSpeechHeard; | |
362 break; | |
363 | |
364 case kErrorNoMatch: | |
365 event_error_code = kErrorNoResults; | |
366 break; | |
367 | |
368 // The remaining kErrorAborted case should never be returned by the server. | |
369 default: | |
370 NOTREACHED(); | |
371 } | |
372 | |
373 if (!event_error_code.empty()) { | |
374 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
375 base::Bind(&SpeechInputExtensionManager::DispatchError, | |
376 this, event_error_code, report_to_event)); | |
377 } | |
378 } | |
379 | |
380 void SpeechInputExtensionManager::DidCompleteEnvironmentEstimation( | |
381 int caller_id) { | |
382 DCHECK_EQ(caller_id, kSpeechCallerId); | |
383 } | |
384 | |
385 void SpeechInputExtensionManager::DidStartReceivingSpeech(int caller_id) { | |
386 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
387 DCHECK_EQ(caller_id, kSpeechCallerId); | |
388 VLOG(1) << "DidStartReceivingSpeech"; | |
389 | |
390 std::string json_args; | |
391 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
392 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, | |
393 this, extension_id_in_use_, std::string(kOnSoundStartEvent), | |
394 json_args)); | |
395 } | |
396 | |
397 void SpeechInputExtensionManager::DidStopReceivingSpeech(int caller_id) { | |
398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
399 DCHECK_EQ(caller_id, kSpeechCallerId); | |
400 VLOG(1) << "DidStopReceivingSpeech"; | |
401 | |
402 std::string json_args; | |
403 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
404 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, | |
405 this, extension_id_in_use_, std::string(kOnSoundEndEvent), | |
406 json_args)); | |
407 } | |
408 | |
409 void SpeechInputExtensionManager::DispatchEventToExtension( | |
410 const std::string& extension_id, const std::string& event, | |
411 const std::string& json_args) { | |
412 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
413 | |
414 base::AutoLock auto_lock(state_lock_); | |
415 if (state_ == kShutdown) | |
416 return; | |
417 | |
418 if (profile_ && profile_->GetExtensionEventRouter()) { | |
419 std::string final_args; | |
420 if (json_args.empty()) { | |
421 ListValue args; | |
422 base::JSONWriter::Write(&args, false, &final_args); | |
423 } else { | |
424 final_args = json_args; | |
425 } | |
426 | |
427 profile_->GetExtensionEventRouter()->DispatchEventToExtension( | |
428 extension_id, event, final_args, profile_, GURL()); | |
429 } | |
430 } | |
431 | |
432 void SpeechInputExtensionManager::DispatchError( | |
433 const std::string& error, bool dispatch_event) { | |
434 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
435 | |
436 std::string extension_id; | |
437 { | |
438 base::AutoLock auto_lock(state_lock_); | |
439 if (state_ == kShutdown) | |
440 return; | |
441 | |
442 if (state_ == kRecording) | |
443 notification_.Hide(); | |
444 | |
445 extension_id = extension_id_in_use_; | |
446 ResetToIdleState(); | |
447 | |
448 // Will set the error property in the ongoing extension function calls. | |
449 ExtensionError details(extension_id, error); | |
450 content::NotificationService::current()->Notify( | |
451 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED, | |
452 content::Source<Profile>(profile_), | |
453 content::Details<ExtensionError>(&details)); | |
454 } | |
455 | |
456 // Used for errors that are also reported via the onError event. | |
457 if (dispatch_event) { | |
458 ListValue args; | |
459 DictionaryValue *js_error = new DictionaryValue(); | |
460 args.Append(js_error); | |
461 js_error->SetString(kErrorCodeKey, error); | |
462 std::string json_args; | |
463 base::JSONWriter::Write(&args, false, &json_args); | |
464 DispatchEventToExtension(extension_id, | |
465 kOnErrorEvent, json_args); | |
466 } | |
467 } | |
468 | |
469 bool SpeechInputExtensionManager::Start(const std::string& extension_id, | |
470 const std::string& language, const std::string& grammar, | |
471 bool filter_profanities, std::string* error) { | |
472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
473 DCHECK(error); | |
474 VLOG(1) << "Requesting start (UI thread)"; | |
475 | |
476 base::AutoLock auto_lock(state_lock_); | |
477 if (state_ == kShutdown || | |
478 (!extension_id_in_use_.empty() && extension_id_in_use_ != extension_id)) { | |
479 *error = kErrorRequestDenied; | |
480 return false; | |
481 } | |
482 | |
483 switch (state_) { | |
484 case kIdle: | |
485 break; | |
486 | |
487 case kStarting: | |
488 *error = kErrorRequestInProgress; | |
489 return false; | |
490 | |
491 case kRecording: | |
492 case kStopping: | |
493 *error = kErrorInvalidOperation; | |
494 return false; | |
495 | |
496 default: | |
497 NOTREACHED(); | |
498 } | |
499 | |
500 extension_id_in_use_ = extension_id; | |
501 VLOG(1) << "State changed to starting"; | |
502 state_ = kStarting; | |
503 | |
504 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
505 base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this, | |
506 profile_->GetRequestContext(), language, grammar, filter_profanities)); | |
507 return true; | |
508 } | |
509 | |
510 void SpeechInputExtensionManager::StartOnIOThread( | |
511 net::URLRequestContextGetter* context_getter, | |
512 const std::string& language, const std::string& grammar, | |
513 bool filter_profanities) { | |
514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
515 VLOG(1) << "Requesting start (IO thread)"; | |
516 | |
517 // Everything put inside the lock to ensure the validity of context_getter, | |
518 // guaranteed while not in the shutdown state. Any ongoing or recognition | |
519 // request will be requested to be aborted when entering the shutdown state. | |
520 base::AutoLock auto_lock(state_lock_); | |
521 if (state_ == kShutdown) | |
522 return; | |
523 | |
524 if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) { | |
525 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
526 base::Bind(&SpeechInputExtensionManager::DispatchError, this, | |
527 std::string(kErrorNoRecordingDeviceFound), false)); | |
528 return; | |
529 } | |
530 | |
531 if (GetSpeechInputExtensionInterface()->IsRecordingInProcess()) { | |
532 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
533 base::Bind(&SpeechInputExtensionManager::DispatchError, this, | |
534 std::string(kErrorRecordingDeviceInUse), false)); | |
535 return; | |
536 } | |
537 | |
538 GetSpeechInputExtensionInterface()->StartRecording(this, context_getter, | |
539 kSpeechCallerId, language, grammar, filter_profanities); | |
540 } | |
541 | |
542 bool SpeechInputExtensionManager::HasAudioInputDevices() { | |
543 return AudioManager::GetAudioManager()->HasAudioInputDevices(); | |
544 } | |
545 | |
546 bool SpeechInputExtensionManager::IsRecordingInProcess() { | |
547 // Thread-safe query. | |
548 return AudioManager::GetAudioManager()->IsRecordingInProcess(); | |
549 } | |
550 | |
551 bool SpeechInputExtensionManager::IsRecording() { | |
552 return GetSpeechInputExtensionInterface()->IsRecordingInProcess(); | |
553 } | |
554 | |
555 void SpeechInputExtensionManager::StartRecording( | |
556 speech_input::SpeechRecognizerDelegate* delegate, | |
557 net::URLRequestContextGetter* context_getter, int caller_id, | |
558 const std::string& language, const std::string& grammar, | |
559 bool filter_profanities) { | |
560 DCHECK(!recognizer_); | |
561 recognizer_ = new SpeechRecognizer(delegate, caller_id, language, grammar, | |
562 context_getter, filter_profanities, "", ""); | |
563 recognizer_->StartRecording(); | |
564 } | |
565 | |
566 bool SpeechInputExtensionManager::HasValidRecognizer() { | |
567 // Conditional expression used to avoid a performance warning on windows. | |
568 return recognizer_ ? true : false; | |
569 } | |
570 | |
571 bool SpeechInputExtensionManager::Stop(const std::string& extension_id, | |
572 std::string* error) { | |
573 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
574 DCHECK(error); | |
575 VLOG(1) << "Requesting stop (UI thread)"; | |
576 | |
577 base::AutoLock auto_lock(state_lock_); | |
578 if (state_ == kShutdown || | |
579 (!extension_id_in_use_.empty() && extension_id_in_use_ != extension_id)) { | |
580 *error = kErrorRequestDenied; | |
581 return false; | |
582 } | |
583 | |
584 switch (state_) { | |
585 case kRecording: | |
586 break; | |
587 | |
588 case kStopping: | |
589 *error = kErrorRequestInProgress; | |
590 return false; | |
591 | |
592 case kIdle: | |
593 case kStarting: | |
594 *error = kErrorInvalidOperation; | |
595 return false; | |
596 | |
597 default: | |
598 NOTREACHED(); | |
599 } | |
600 | |
601 // Guarded by the state lock. | |
602 DCHECK(GetSpeechInputExtensionInterface()->HasValidRecognizer()); | |
603 | |
604 VLOG(1) << "State changed to stopping"; | |
605 state_ = kStopping; | |
606 | |
607 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
608 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); | |
609 return true; | |
610 } | |
611 | |
612 void SpeechInputExtensionManager::ForceStopOnIOThread() { | |
613 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
614 VLOG(1) << "Requesting forced stop (IO thread)"; | |
615 | |
616 base::AutoLock auto_lock(state_lock_); | |
617 DCHECK(state_ != kIdle); | |
618 | |
619 GetSpeechInputExtensionInterface()->StopRecording(false); | |
620 | |
621 if (state_ == kShutdown) | |
622 return; | |
623 | |
624 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
625 base::Bind(&SpeechInputExtensionManager::StopSucceededOnUIThread, this)); | |
626 } | |
627 | |
628 void SpeechInputExtensionManager::StopRecording(bool recognition_failed) { | |
629 if (recognizer_) { | |
630 // Recognition is already cancelled in case of failure. | |
631 // Double-cancelling leads to assertion failures. | |
632 if (!recognition_failed) | |
633 recognizer_->CancelRecognition(); | |
634 recognizer_.release(); | |
635 } | |
636 } | |
637 | |
638 void SpeechInputExtensionManager::StopSucceededOnUIThread() { | |
639 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
640 VLOG(1) << "Stop succeeded (UI thread)"; | |
641 | |
642 base::AutoLock auto_lock(state_lock_); | |
643 if (state_ == kShutdown) | |
644 return; | |
645 | |
646 std::string extension_id = extension_id_in_use_; | |
647 ResetToIdleState(); | |
648 | |
649 notification_.Hide(); | |
650 | |
651 content::NotificationService::current()->Notify( | |
652 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED, | |
653 // Guarded by the state_ == kShutdown check. | |
654 content::Source<Profile>(profile_), | |
655 content::Details<std::string>(&extension_id)); | |
656 } | |
657 | |
658 void SpeechInputExtensionManager::SetInputVolume(int caller_id, | |
659 float volume, | |
660 float noise_volume) { | |
661 DCHECK_EQ(caller_id, kSpeechCallerId); | |
662 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
663 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
664 base::Bind(&SpeechInputExtensionManager::SetInputVolumeOnUIThread, | |
665 this, volume)); | |
666 } | |
667 | |
668 void SpeechInputExtensionManager::SetInputVolumeOnUIThread( | |
669 float volume) { | |
670 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
671 notification_.SetVUMeterVolume(volume); | |
672 } | |
OLD | NEW |