OLD | NEW |
| (Empty) |
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. | |
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/lazy_instance.h" | |
10 #include "base/utf_string_conversions.h" | |
11 #include "base/values.h" | |
12 #include "chrome/browser/extensions/event_router.h" | |
13 #include "chrome/browser/extensions/extension_function_registry.h" | |
14 #include "chrome/browser/extensions/extension_host.h" | |
15 #include "chrome/browser/extensions/extension_process_manager.h" | |
16 #include "chrome/browser/extensions/extension_service.h" | |
17 #include "chrome/browser/extensions/extension_system.h" | |
18 #include "chrome/browser/profiles/profile.h" | |
19 #include "chrome/browser/profiles/profile_dependency_manager.h" | |
20 #include "chrome/browser/profiles/profile_keyed_service.h" | |
21 #include "chrome/browser/profiles/profile_keyed_service_factory.h" | |
22 #include "chrome/browser/speech/speech_input_extension_api.h" | |
23 #include "chrome/common/chrome_notification_types.h" | |
24 #include "chrome/common/extensions/extension.h" | |
25 #include "content/public/browser/browser_thread.h" | |
26 #include "content/public/browser/notification_registrar.h" | |
27 #include "content/public/browser/notification_service.h" | |
28 #include "content/public/browser/render_process_host.h" | |
29 #include "content/public/browser/speech_recognition_manager.h" | |
30 #include "content/public/browser/speech_recognition_session_config.h" | |
31 #include "content/public/browser/speech_recognition_session_context.h" | |
32 #include "content/public/common/speech_recognition_error.h" | |
33 #include "content/public/common/speech_recognition_result.h" | |
34 #include "net/url_request/url_request_context_getter.h" | |
35 | |
36 using content::BrowserThread; | |
37 using content::SpeechRecognitionHypothesis; | |
38 using content::SpeechRecognitionManager; | |
39 | |
40 namespace { | |
41 | |
42 const char kErrorNoRecordingDeviceFound[] = "noRecordingDeviceFound"; | |
43 const char kErrorRecordingDeviceInUse[] = "recordingDeviceInUse"; | |
44 const char kErrorUnableToStart[] = "unableToStart"; | |
45 const char kErrorRequestDenied[] = "requestDenied"; | |
46 const char kErrorRequestInProgress[] = "requestInProgress"; | |
47 const char kErrorInvalidOperation[] = "invalidOperation"; | |
48 | |
49 const char kErrorCodeKey[] = "code"; | |
50 const char kErrorCaptureError[] = "captureError"; | |
51 const char kErrorNetworkError[] = "networkError"; | |
52 const char kErrorNoSpeechHeard[] = "noSpeechHeard"; | |
53 const char kErrorNoResults[] = "noResults"; | |
54 | |
55 const char kUtteranceKey[] = "utterance"; | |
56 const char kConfidenceKey[] = "confidence"; | |
57 const char kHypothesesKey[] = "hypotheses"; | |
58 | |
59 const char kOnErrorEvent[] = "experimental.speechInput.onError"; | |
60 const char kOnResultEvent[] = "experimental.speechInput.onResult"; | |
61 const char kOnSoundStartEvent[] = "experimental.speechInput.onSoundStart"; | |
62 const char kOnSoundEndEvent[] = "experimental.speechInput.onSoundEnd"; | |
63 | |
64 } | |
65 | |
66 SpeechInputExtensionInterface::SpeechInputExtensionInterface() { | |
67 } | |
68 | |
69 SpeechInputExtensionInterface::~SpeechInputExtensionInterface() { | |
70 } | |
71 | |
72 SpeechInputExtensionManager::SpeechInputExtensionManager(Profile* profile) | |
73 : profile_(profile), | |
74 state_(kIdle), | |
75 registrar_(new content::NotificationRegistrar), | |
76 speech_interface_(NULL), | |
77 is_recognition_in_progress_(false), | |
78 speech_recognition_session_id_( | |
79 SpeechRecognitionManager::kSessionIDInvalid) { | |
80 registrar_->Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, | |
81 content::Source<Profile>(profile_)); | |
82 } | |
83 | |
84 SpeechInputExtensionManager::~SpeechInputExtensionManager() { | |
85 } | |
86 | |
87 SpeechInputExtensionManager* SpeechInputExtensionManager::GetForProfile( | |
88 Profile* profile) { | |
89 extensions::SpeechInputAPI* speech_input_api = | |
90 extensions::ProfileKeyedAPIFactory<extensions::SpeechInputAPI>:: | |
91 GetForProfile(profile); | |
92 if (!speech_input_api) | |
93 return NULL; | |
94 return speech_input_api->manager(); | |
95 } | |
96 | |
97 void SpeechInputExtensionManager::Observe(int type, | |
98 const content::NotificationSource& source, | |
99 const content::NotificationDetails& details) { | |
100 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) { | |
101 ExtensionUnloaded( | |
102 content::Details<extensions::UnloadedExtensionInfo>(details)-> | |
103 extension->id()); | |
104 } else { | |
105 NOTREACHED(); | |
106 } | |
107 } | |
108 | |
109 void SpeechInputExtensionManager::ShutdownOnUIThread() { | |
110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
111 VLOG(1) << "Profile shutting down."; | |
112 | |
113 // Note: Unretained(this) is safe, also if we are freed in the meanwhile. | |
114 // It is used by the SR manager just for comparing the raw pointer and remove | |
115 // the associated sessions. | |
116 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
117 base::Bind(&SpeechInputExtensionManager::AbortAllSessionsOnIOThread, | |
118 base::Unretained(this))); | |
119 | |
120 base::AutoLock auto_lock(state_lock_); | |
121 DCHECK(state_ != kShutdown); | |
122 if (state_ != kIdle) { | |
123 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
124 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); | |
125 } | |
126 state_ = kShutdown; | |
127 VLOG(1) << "Entering the shutdown sink state."; | |
128 registrar_.reset(); | |
129 profile_ = NULL; | |
130 } | |
131 | |
132 void SpeechInputExtensionManager::AbortAllSessionsOnIOThread() { | |
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
134 // TODO(primiano): The following check should not be really needed if the | |
135 // SpeechRecognitionManager and this class are destroyed in the correct order | |
136 // (this class first), as it is in current chrome implementation. | |
137 // However, it seems the some ChromiumOS tests violate the destruction order | |
138 // envisaged by browser_main_loop, so SpeechRecognitionmanager could have been | |
139 // freed by now. | |
140 if (SpeechRecognitionManager* mgr = SpeechRecognitionManager::GetInstance()) | |
141 mgr->AbortAllSessionsForListener(this); | |
142 } | |
143 | |
144 void SpeechInputExtensionManager::ExtensionUnloaded( | |
145 const std::string& extension_id) { | |
146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
147 | |
148 base::AutoLock auto_lock(state_lock_); | |
149 if (state_ == kShutdown) | |
150 return; | |
151 | |
152 VLOG(1) << "Extension unloaded. Requesting to enforce stop..."; | |
153 if (extension_id_in_use_ == extension_id) { | |
154 if (state_ != kIdle) { | |
155 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
156 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); | |
157 } | |
158 } | |
159 } | |
160 | |
161 void SpeechInputExtensionManager::SetSpeechInputExtensionInterface( | |
162 SpeechInputExtensionInterface* speech_interface) { | |
163 speech_interface_ = speech_interface; | |
164 } | |
165 | |
166 SpeechInputExtensionInterface* | |
167 SpeechInputExtensionManager::GetSpeechInputExtensionInterface() { | |
168 return speech_interface_ ? speech_interface_ : this; | |
169 } | |
170 | |
171 void SpeechInputExtensionManager::ResetToIdleState() { | |
172 VLOG(1) << "State changed to idle. Deassociating any extensions."; | |
173 state_ = kIdle; | |
174 extension_id_in_use_.clear(); | |
175 } | |
176 | |
177 int SpeechInputExtensionManager::GetRenderProcessIDForExtension( | |
178 const std::string& extension_id) const { | |
179 ExtensionProcessManager* epm = | |
180 extensions::ExtensionSystem::Get(profile_)->process_manager(); | |
181 DCHECK(epm); | |
182 extensions::ExtensionHost* eh = | |
183 epm->GetBackgroundHostForExtension(extension_id); | |
184 DCHECK(eh); | |
185 content::RenderProcessHost* rph = eh->render_process_host(); | |
186 DCHECK(rph); | |
187 return rph->GetID(); | |
188 } | |
189 | |
190 void SpeechInputExtensionManager::OnRecognitionResults( | |
191 int session_id, | |
192 const content::SpeechRecognitionResults& results) { | |
193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
194 DCHECK_EQ(session_id, speech_recognition_session_id_); | |
195 | |
196 // Stopping will start the disassociation with the extension. | |
197 // Make a copy to report the results to the proper one. | |
198 std::string extension_id = extension_id_in_use_; | |
199 ForceStopOnIOThread(); | |
200 | |
201 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
202 base::Bind(&SpeechInputExtensionManager::SetRecognitionResultsOnUIThread, | |
203 this, results, extension_id)); | |
204 } | |
205 | |
206 void SpeechInputExtensionManager::SetRecognitionResultsOnUIThread( | |
207 const content::SpeechRecognitionResults& results, | |
208 const std::string& extension_id) { | |
209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
210 | |
211 content::SpeechRecognitionResults::const_iterator it = results.begin(); | |
212 for (; it != results.end(); ++it) { | |
213 const content::SpeechRecognitionResult& result = (*it); | |
214 | |
215 scoped_ptr<ListValue> args(new ListValue()); | |
216 DictionaryValue* js_event = new DictionaryValue(); | |
217 args->Append(js_event); | |
218 | |
219 ListValue* js_hypothesis_array = new ListValue(); | |
220 js_event->Set(kHypothesesKey, js_hypothesis_array); | |
221 | |
222 for (size_t i = 0; i < result.hypotheses.size(); ++i) { | |
223 const SpeechRecognitionHypothesis& hypothesis = result.hypotheses[i]; | |
224 | |
225 DictionaryValue* js_hypothesis_object = new DictionaryValue(); | |
226 js_hypothesis_array->Append(js_hypothesis_object); | |
227 | |
228 js_hypothesis_object->SetString(kUtteranceKey, | |
229 UTF16ToUTF8(hypothesis.utterance)); | |
230 js_hypothesis_object->SetDouble(kConfidenceKey, | |
231 hypothesis.confidence); | |
232 } | |
233 | |
234 DispatchEventToExtension(extension_id, kOnResultEvent, args.Pass()); | |
235 } | |
236 } | |
237 | |
238 void SpeechInputExtensionManager::OnRecognitionStart(int session_id) { | |
239 DCHECK_EQ(session_id, speech_recognition_session_id_); | |
240 } | |
241 | |
242 void SpeechInputExtensionManager::OnAudioStart(int session_id) { | |
243 VLOG(1) << "OnAudioStart"; | |
244 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
245 DCHECK_EQ(session_id, speech_recognition_session_id_); | |
246 | |
247 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
248 base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread, | |
249 this)); | |
250 } | |
251 | |
252 void SpeechInputExtensionManager::OnAudioEnd(int session_id) { | |
253 } | |
254 | |
255 void SpeechInputExtensionManager::OnRecognitionEnd(int session_id) { | |
256 // In the very exceptional case in which we requested a new recognition before | |
257 // the previous one ended, don't clobber the speech_recognition_session_id_. | |
258 if (speech_recognition_session_id_ == session_id) { | |
259 is_recognition_in_progress_ = false; | |
260 speech_recognition_session_id_ = | |
261 SpeechRecognitionManager::kSessionIDInvalid; | |
262 } | |
263 } | |
264 | |
265 void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { | |
266 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
267 | |
268 base::AutoLock auto_lock(state_lock_); | |
269 if (state_ == kShutdown) | |
270 return; | |
271 | |
272 DCHECK_EQ(state_, kStarting); | |
273 VLOG(1) << "State changed to recording"; | |
274 state_ = kRecording; | |
275 | |
276 DCHECK(profile_); | |
277 | |
278 VLOG(1) << "Sending start notification"; | |
279 content::NotificationService::current()->Notify( | |
280 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED, | |
281 content::Source<Profile>(profile_), | |
282 content::Details<std::string>(&extension_id_in_use_)); | |
283 } | |
284 | |
285 void SpeechInputExtensionManager::OnRecognitionError( | |
286 int session_id, const content::SpeechRecognitionError& error) { | |
287 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
288 DCHECK_EQ(session_id, speech_recognition_session_id_); | |
289 VLOG(1) << "OnRecognitionError: " << error.code; | |
290 | |
291 base::AutoLock auto_lock(state_lock_); | |
292 if (state_ == kShutdown) | |
293 return; | |
294 | |
295 GetSpeechInputExtensionInterface()->StopRecording(true); | |
296 | |
297 std::string event_error_code; | |
298 bool report_to_event = true; | |
299 | |
300 switch (error.code) { | |
301 case content::SPEECH_RECOGNITION_ERROR_NONE: | |
302 break; | |
303 | |
304 case content::SPEECH_RECOGNITION_ERROR_ABORTED: | |
305 // ERROR_ABORTED is received whenever AbortSession is called on the | |
306 // manager. However, we want propagate the error only if it is triggered | |
307 // by an external cause (another recognition started, aborting us), thus | |
308 // only if it occurs while we are capturing audio. | |
309 if (state_ == kRecording) | |
310 event_error_code = kErrorCaptureError; | |
311 break; | |
312 | |
313 case content::SPEECH_RECOGNITION_ERROR_AUDIO: | |
314 if (state_ == kStarting) { | |
315 event_error_code = kErrorUnableToStart; | |
316 report_to_event = false; | |
317 } else { | |
318 event_error_code = kErrorCaptureError; | |
319 } | |
320 break; | |
321 | |
322 case content::SPEECH_RECOGNITION_ERROR_NETWORK: | |
323 event_error_code = kErrorNetworkError; | |
324 break; | |
325 | |
326 case content::SPEECH_RECOGNITION_ERROR_BAD_GRAMMAR: | |
327 // No error is returned on invalid language, for example. | |
328 // To avoid confusion about when this is would be fired, the invalid | |
329 // params error is not being exposed to the onError event. | |
330 event_error_code = kErrorUnableToStart; | |
331 break; | |
332 | |
333 case content::SPEECH_RECOGNITION_ERROR_NO_SPEECH: | |
334 event_error_code = kErrorNoSpeechHeard; | |
335 break; | |
336 | |
337 case content::SPEECH_RECOGNITION_ERROR_NO_MATCH: | |
338 event_error_code = kErrorNoResults; | |
339 break; | |
340 | |
341 // The remaining kErrorAborted case should never be returned by the server. | |
342 default: | |
343 NOTREACHED(); | |
344 } | |
345 | |
346 if (!event_error_code.empty()) { | |
347 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
348 base::Bind(&SpeechInputExtensionManager::DispatchError, | |
349 this, event_error_code, report_to_event)); | |
350 } | |
351 } | |
352 | |
353 void SpeechInputExtensionManager::OnEnvironmentEstimationComplete( | |
354 int session_id) { | |
355 DCHECK_EQ(session_id, speech_recognition_session_id_); | |
356 } | |
357 | |
358 void SpeechInputExtensionManager::OnSoundStart(int session_id) { | |
359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
360 DCHECK_EQ(session_id, speech_recognition_session_id_); | |
361 VLOG(1) << "OnSoundStart"; | |
362 | |
363 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
364 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, | |
365 this, extension_id_in_use_, std::string(kOnSoundStartEvent), | |
366 Passed(scoped_ptr<ListValue>(new ListValue())))); | |
367 } | |
368 | |
369 void SpeechInputExtensionManager::OnSoundEnd(int session_id) { | |
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
371 VLOG(1) << "OnSoundEnd"; | |
372 | |
373 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
374 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, | |
375 this, extension_id_in_use_, std::string(kOnSoundEndEvent), | |
376 Passed(scoped_ptr<ListValue>(new ListValue())))); | |
377 } | |
378 | |
379 void SpeechInputExtensionManager::DispatchEventToExtension( | |
380 const std::string& extension_id, const std::string& event_name, | |
381 scoped_ptr<ListValue> event_args) { | |
382 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
383 | |
384 base::AutoLock auto_lock(state_lock_); | |
385 if (state_ == kShutdown) | |
386 return; | |
387 | |
388 if (profile_ && extensions::ExtensionSystem::Get(profile_)->event_router()) { | |
389 scoped_ptr<extensions::Event> event(new extensions::Event( | |
390 event_name, event_args.Pass())); | |
391 event->restrict_to_profile = profile_; | |
392 extensions::ExtensionSystem::Get(profile_)->event_router()-> | |
393 DispatchEventToExtension(extension_id, event.Pass()); | |
394 } | |
395 } | |
396 | |
397 void SpeechInputExtensionManager::DispatchError( | |
398 const std::string& error, bool dispatch_event) { | |
399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
400 | |
401 std::string extension_id; | |
402 { | |
403 base::AutoLock auto_lock(state_lock_); | |
404 if (state_ == kShutdown) | |
405 return; | |
406 | |
407 extension_id = extension_id_in_use_; | |
408 ResetToIdleState(); | |
409 | |
410 // Will set the error property in the ongoing extension function calls. | |
411 ExtensionError details(extension_id, error); | |
412 content::NotificationService::current()->Notify( | |
413 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED, | |
414 content::Source<Profile>(profile_), | |
415 content::Details<ExtensionError>(&details)); | |
416 } | |
417 | |
418 // Used for errors that are also reported via the onError event. | |
419 if (dispatch_event) { | |
420 scoped_ptr<ListValue> args(new ListValue()); | |
421 DictionaryValue* js_error = new DictionaryValue(); | |
422 args->Append(js_error); | |
423 js_error->SetString(kErrorCodeKey, error); | |
424 DispatchEventToExtension(extension_id, kOnErrorEvent, args.Pass()); | |
425 } | |
426 } | |
427 | |
428 bool SpeechInputExtensionManager::Start( | |
429 const std::string& extension_id, const std::string& language, | |
430 const std::string& grammar, bool filter_profanities, std::string* error) { | |
431 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
432 DCHECK(error); | |
433 VLOG(1) << "Requesting start (UI thread)"; | |
434 | |
435 base::AutoLock auto_lock(state_lock_); | |
436 if (state_ == kShutdown || | |
437 (!extension_id_in_use_.empty() && extension_id_in_use_ != extension_id)) { | |
438 *error = kErrorRequestDenied; | |
439 return false; | |
440 } | |
441 | |
442 switch (state_) { | |
443 case kIdle: | |
444 break; | |
445 | |
446 case kStarting: | |
447 *error = kErrorRequestInProgress; | |
448 return false; | |
449 | |
450 case kRecording: | |
451 case kStopping: | |
452 *error = kErrorInvalidOperation; | |
453 return false; | |
454 | |
455 default: | |
456 NOTREACHED(); | |
457 } | |
458 | |
459 const extensions::Extension* extension = | |
460 extensions::ExtensionSystem::Get(profile_)->extension_service()-> | |
461 GetExtensionById(extension_id, true); | |
462 DCHECK(extension); | |
463 const std::string& extension_name = extension->name(); | |
464 | |
465 extension_id_in_use_ = extension_id; | |
466 VLOG(1) << "State changed to starting"; | |
467 state_ = kStarting; | |
468 | |
469 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter = | |
470 profile_->GetRequestContext(); | |
471 | |
472 const int render_process_id = GetRenderProcessIDForExtension(extension_id); | |
473 | |
474 BrowserThread::PostTask( | |
475 BrowserThread::IO, FROM_HERE, | |
476 base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this, | |
477 url_request_context_getter, extension_name, language, grammar, | |
478 filter_profanities, render_process_id)); | |
479 return true; | |
480 } | |
481 | |
482 void SpeechInputExtensionManager::StartOnIOThread( | |
483 scoped_refptr<net::URLRequestContextGetter> context_getter, | |
484 const std::string& extension_name, | |
485 const std::string& language, | |
486 const std::string& grammar, | |
487 bool filter_profanities, | |
488 int render_process_id) { | |
489 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
490 VLOG(1) << "Requesting start (IO thread)"; | |
491 | |
492 // Everything put inside the lock to ensure the validity of context_getter, | |
493 // guaranteed while not in the shutdown state. Any ongoing or recognition | |
494 // request will be requested to be aborted when entering the shutdown state. | |
495 base::AutoLock auto_lock(state_lock_); | |
496 if (state_ == kShutdown) | |
497 return; | |
498 | |
499 // TODO(primiano): These two checks below could be avoided, since they are | |
500 // already handled in the speech recognition classes. However, since the | |
501 // speech input extensions tests are bypassing the manager, we need them to | |
502 // pass the tests. | |
503 if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) { | |
504 BrowserThread::PostTask( | |
505 BrowserThread::UI, FROM_HERE, | |
506 base::Bind(&SpeechInputExtensionManager::DispatchError, this, | |
507 std::string(kErrorNoRecordingDeviceFound), false)); | |
508 return; | |
509 } | |
510 | |
511 if (GetSpeechInputExtensionInterface()->IsCapturingAudio()) { | |
512 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
513 base::Bind(&SpeechInputExtensionManager::DispatchError, this, | |
514 std::string(kErrorRecordingDeviceInUse), false)); | |
515 return; | |
516 } | |
517 | |
518 GetSpeechInputExtensionInterface()->StartRecording(this, | |
519 context_getter, | |
520 extension_name, | |
521 language, | |
522 grammar, | |
523 filter_profanities, | |
524 render_process_id); | |
525 } | |
526 | |
527 bool SpeechInputExtensionManager::HasAudioInputDevices() { | |
528 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
529 return SpeechRecognitionManager::GetInstance()->HasAudioInputDevices(); | |
530 } | |
531 | |
532 bool SpeechInputExtensionManager::IsCapturingAudio() { | |
533 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
534 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); | |
535 } | |
536 | |
537 void SpeechInputExtensionManager::IsRecording( | |
538 const IsRecordingCallback& callback) { | |
539 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
540 BrowserThread::PostTask( | |
541 BrowserThread::IO, FROM_HERE, | |
542 base::Bind(&SpeechInputExtensionManager::IsRecordingOnIOThread, | |
543 this, callback)); | |
544 } | |
545 | |
546 void SpeechInputExtensionManager::IsRecordingOnIOThread( | |
547 const IsRecordingCallback& callback) { | |
548 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
549 | |
550 bool result = GetSpeechInputExtensionInterface()->IsCapturingAudio(); | |
551 | |
552 BrowserThread::PostTask( | |
553 BrowserThread::UI, FROM_HERE, | |
554 base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread, | |
555 this, callback, result)); | |
556 } | |
557 | |
558 void SpeechInputExtensionManager::IsRecordingOnUIThread( | |
559 const IsRecordingCallback& callback, | |
560 bool result) { | |
561 BrowserThread::CurrentlyOn(BrowserThread::UI); | |
562 callback.Run(result); | |
563 } | |
564 | |
565 void SpeechInputExtensionManager::StartRecording( | |
566 content::SpeechRecognitionEventListener* listener, | |
567 net::URLRequestContextGetter* context_getter, | |
568 const std::string& extension_name, | |
569 const std::string& language, | |
570 const std::string& grammar, | |
571 bool filter_profanities, | |
572 int render_process_id) { | |
573 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
574 | |
575 content::SpeechRecognitionSessionContext context; | |
576 context.requested_by_page_element = false; | |
577 context.render_process_id = render_process_id; | |
578 context.context_name = extension_name; | |
579 | |
580 content::SpeechRecognitionSessionConfig config; | |
581 config.language = language; | |
582 config.grammars.push_back(content::SpeechRecognitionGrammar(grammar)); | |
583 config.initial_context = context; | |
584 config.url_request_context_getter = context_getter; | |
585 config.filter_profanities = filter_profanities; | |
586 config.event_listener = listener; | |
587 | |
588 DCHECK(!is_recognition_in_progress_); | |
589 SpeechRecognitionManager& manager = *SpeechRecognitionManager::GetInstance(); | |
590 speech_recognition_session_id_ = | |
591 manager.CreateSession(config); | |
592 DCHECK_NE(speech_recognition_session_id_, | |
593 SpeechRecognitionManager::kSessionIDInvalid); | |
594 is_recognition_in_progress_ = true; | |
595 manager.StartSession(speech_recognition_session_id_); | |
596 } | |
597 | |
598 bool SpeechInputExtensionManager::HasValidRecognizer() { | |
599 if (!is_recognition_in_progress_) | |
600 return false; | |
601 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); | |
602 } | |
603 | |
604 bool SpeechInputExtensionManager::Stop(const std::string& extension_id, | |
605 std::string* error) { | |
606 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
607 DCHECK(error); | |
608 VLOG(1) << "Requesting stop (UI thread)"; | |
609 | |
610 base::AutoLock auto_lock(state_lock_); | |
611 if (state_ == kShutdown || | |
612 (!extension_id_in_use_.empty() && extension_id_in_use_ != extension_id)) { | |
613 *error = kErrorRequestDenied; | |
614 return false; | |
615 } | |
616 | |
617 switch (state_) { | |
618 case kRecording: | |
619 break; | |
620 | |
621 case kStopping: | |
622 *error = kErrorRequestInProgress; | |
623 return false; | |
624 | |
625 case kIdle: | |
626 case kStarting: | |
627 *error = kErrorInvalidOperation; | |
628 return false; | |
629 | |
630 default: | |
631 NOTREACHED(); | |
632 } | |
633 | |
634 // Guarded by the state lock. | |
635 DCHECK(GetSpeechInputExtensionInterface()->HasValidRecognizer()); | |
636 | |
637 VLOG(1) << "State changed to stopping"; | |
638 state_ = kStopping; | |
639 | |
640 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
641 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); | |
642 return true; | |
643 } | |
644 | |
645 void SpeechInputExtensionManager::ForceStopOnIOThread() { | |
646 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
647 VLOG(1) << "Requesting forced stop (IO thread)"; | |
648 | |
649 base::AutoLock auto_lock(state_lock_); | |
650 DCHECK(state_ != kIdle); | |
651 | |
652 GetSpeechInputExtensionInterface()->StopRecording(false); | |
653 | |
654 if (state_ == kShutdown) | |
655 return; | |
656 | |
657 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
658 base::Bind(&SpeechInputExtensionManager::StopSucceededOnUIThread, this)); | |
659 } | |
660 | |
661 void SpeechInputExtensionManager::StopRecording(bool recognition_failed) { | |
662 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
663 if (!is_recognition_in_progress_) | |
664 return; | |
665 DCHECK_NE(speech_recognition_session_id_, | |
666 SpeechRecognitionManager::kSessionIDInvalid); | |
667 SpeechRecognitionManager::GetInstance()->AbortSession( | |
668 speech_recognition_session_id_); | |
669 } | |
670 | |
671 void SpeechInputExtensionManager::StopSucceededOnUIThread() { | |
672 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
673 VLOG(1) << "Stop succeeded (UI thread)"; | |
674 | |
675 base::AutoLock auto_lock(state_lock_); | |
676 if (state_ == kShutdown) | |
677 return; | |
678 | |
679 std::string extension_id = extension_id_in_use_; | |
680 ResetToIdleState(); | |
681 | |
682 content::NotificationService::current()->Notify( | |
683 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED, | |
684 // Guarded by the state_ == kShutdown check. | |
685 content::Source<Profile>(profile_), | |
686 content::Details<std::string>(&extension_id)); | |
687 } | |
688 | |
689 void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id, | |
690 float volume, | |
691 float noise_volume) {} | |
692 | |
693 namespace extensions { | |
694 | |
695 SpeechInputAPI::SpeechInputAPI(Profile* profile) | |
696 : manager_(new SpeechInputExtensionManager(profile)) { | |
697 ExtensionFunctionRegistry* registry = | |
698 ExtensionFunctionRegistry::GetInstance(); | |
699 registry->RegisterFunction<StartSpeechInputFunction>(); | |
700 registry->RegisterFunction<StopSpeechInputFunction>(); | |
701 registry->RegisterFunction<IsRecordingSpeechInputFunction>(); | |
702 } | |
703 | |
704 SpeechInputAPI::~SpeechInputAPI() { | |
705 } | |
706 | |
707 void SpeechInputAPI::Shutdown() { | |
708 manager_->ShutdownOnUIThread(); | |
709 } | |
710 | |
711 static base::LazyInstance<ProfileKeyedAPIFactory<SpeechInputAPI> > | |
712 g_factory = LAZY_INSTANCE_INITIALIZER; | |
713 | |
714 // static | |
715 ProfileKeyedAPIFactory<SpeechInputAPI>* SpeechInputAPI::GetFactoryInstance() { | |
716 return &g_factory.Get(); | |
717 } | |
718 | |
719 } // namespace extensions | |
OLD | NEW |