OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/speech/speech_input_extension_manager.h" | 5 #include "chrome/browser/speech/speech_input_extension_manager.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/json/json_writer.h" | 8 #include "base/json/json_writer.h" |
9 #include "base/utf_string_conversions.h" | 9 #include "base/utf_string_conversions.h" |
10 #include "base/values.h" | 10 #include "base/values.h" |
11 #include "chrome/browser/extensions/extension_event_router.h" | 11 #include "chrome/browser/extensions/extension_event_router.h" |
12 #include "chrome/browser/extensions/extension_service.h" | 12 #include "chrome/browser/extensions/extension_service.h" |
13 #include "chrome/browser/prefs/pref_service.h" | 13 #include "chrome/browser/prefs/pref_service.h" |
14 #include "chrome/browser/profiles/profile.h" | 14 #include "chrome/browser/profiles/profile.h" |
15 #include "chrome/browser/profiles/profile_dependency_manager.h" | 15 #include "chrome/browser/profiles/profile_dependency_manager.h" |
16 #include "chrome/browser/profiles/profile_keyed_service.h" | 16 #include "chrome/browser/profiles/profile_keyed_service.h" |
17 #include "chrome/browser/profiles/profile_keyed_service_factory.h" | 17 #include "chrome/browser/profiles/profile_keyed_service_factory.h" |
18 #include "chrome/browser/speech/speech_recognition_tray_icon_controller.h" | |
19 #include "chrome/common/chrome_notification_types.h" | 18 #include "chrome/common/chrome_notification_types.h" |
20 #include "chrome/common/extensions/extension.h" | 19 #include "chrome/common/extensions/extension.h" |
21 #include "chrome/common/pref_names.h" | 20 #include "chrome/common/pref_names.h" |
22 #include "content/public/browser/browser_thread.h" | 21 #include "content/public/browser/browser_thread.h" |
23 #include "content/public/browser/notification_registrar.h" | 22 #include "content/public/browser/notification_registrar.h" |
24 #include "content/public/browser/notification_service.h" | 23 #include "content/public/browser/notification_service.h" |
25 #include "content/public/browser/speech_recognition_manager.h" | 24 #include "content/public/browser/speech_recognition_manager.h" |
26 #include "content/public/browser/speech_recognizer.h" | 25 #include "content/public/browser/speech_recognition_session_config.h" |
26 #include "content/public/browser/speech_recognition_session_context.h" | |
27 #include "content/public/common/speech_recognition_error.h" | 27 #include "content/public/common/speech_recognition_error.h" |
28 #include "content/public/common/speech_recognition_result.h" | 28 #include "content/public/common/speech_recognition_result.h" |
29 #include "net/url_request/url_request_context_getter.h" | |
29 | 30 |
30 using content::BrowserThread; | 31 using content::BrowserThread; |
31 using content::SpeechRecognitionHypothesis; | 32 using content::SpeechRecognitionHypothesis; |
32 using content::SpeechRecognitionManager; | 33 using content::SpeechRecognitionManager; |
33 | 34 |
34 namespace { | 35 namespace { |
35 | 36 |
36 const char kErrorNoRecordingDeviceFound[] = "noRecordingDeviceFound"; | 37 const char kErrorNoRecordingDeviceFound[] = "noRecordingDeviceFound"; |
37 const char kErrorRecordingDeviceInUse[] = "recordingDeviceInUse"; | 38 const char kErrorRecordingDeviceInUse[] = "recordingDeviceInUse"; |
38 const char kErrorUnableToStart[] = "unableToStart"; | 39 const char kErrorUnableToStart[] = "unableToStart"; |
39 const char kErrorRequestDenied[] = "requestDenied"; | 40 const char kErrorRequestDenied[] = "requestDenied"; |
40 const char kErrorRequestInProgress[] = "requestInProgress"; | 41 const char kErrorRequestInProgress[] = "requestInProgress"; |
41 const char kErrorInvalidOperation[] = "invalidOperation"; | 42 const char kErrorInvalidOperation[] = "invalidOperation"; |
42 | 43 |
43 const char kErrorCodeKey[] = "code"; | 44 const char kErrorCodeKey[] = "code"; |
44 const char kErrorCaptureError[] = "captureError"; | 45 const char kErrorCaptureError[] = "captureError"; |
45 const char kErrorNetworkError[] = "networkError"; | 46 const char kErrorNetworkError[] = "networkError"; |
46 const char kErrorNoSpeechHeard[] = "noSpeechHeard"; | 47 const char kErrorNoSpeechHeard[] = "noSpeechHeard"; |
47 const char kErrorNoResults[] = "noResults"; | 48 const char kErrorNoResults[] = "noResults"; |
48 | 49 |
49 const char kUtteranceKey[] = "utterance"; | 50 const char kUtteranceKey[] = "utterance"; |
50 const char kConfidenceKey[] = "confidence"; | 51 const char kConfidenceKey[] = "confidence"; |
51 const char kHypothesesKey[] = "hypotheses"; | 52 const char kHypothesesKey[] = "hypotheses"; |
52 | 53 |
53 const char kOnErrorEvent[] = "experimental.speechInput.onError"; | 54 const char kOnErrorEvent[] = "experimental.speechInput.onError"; |
54 const char kOnResultEvent[] = "experimental.speechInput.onResult"; | 55 const char kOnResultEvent[] = "experimental.speechInput.onResult"; |
55 const char kOnSoundStartEvent[] = "experimental.speechInput.onSoundStart"; | 56 const char kOnSoundStartEvent[] = "experimental.speechInput.onSoundStart"; |
56 const char kOnSoundEndEvent[] = "experimental.speechInput.onSoundEnd"; | 57 const char kOnSoundEndEvent[] = "experimental.speechInput.onSoundEnd"; |
57 | 58 |
58 // Session id provided to the speech recognizer. Since only one extension can | |
59 // be recording on the same time a constant value is enough as id. | |
60 // TODO(primiano) this will not be valid anymore once speech input extension | |
61 // will use the SpeechRecognitionManager and not the SpeechRecognizer directly. | |
62 static const int kSpeechInputSessionId = 1; | |
63 | |
64 // Wrap an SpeechInputExtensionManager using scoped_refptr to avoid | 59 // Wrap an SpeechInputExtensionManager using scoped_refptr to avoid |
65 // assertion failures on destruction because of not using release(). | 60 // assertion failures on destruction because of not using release(). |
66 class SpeechInputExtensionManagerWrapper : public ProfileKeyedService { | 61 class SpeechInputExtensionManagerWrapper : public ProfileKeyedService { |
67 public: | 62 public: |
68 explicit SpeechInputExtensionManagerWrapper( | 63 explicit SpeechInputExtensionManagerWrapper( |
69 SpeechInputExtensionManager* manager) | 64 SpeechInputExtensionManager* manager) |
70 : manager_(manager) {} | 65 : manager_(manager) {} |
71 | 66 |
72 virtual ~SpeechInputExtensionManagerWrapper() {} | 67 virtual ~SpeechInputExtensionManagerWrapper() {} |
73 | 68 |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
150 SpeechInputExtensionInterface::SpeechInputExtensionInterface() { | 145 SpeechInputExtensionInterface::SpeechInputExtensionInterface() { |
151 } | 146 } |
152 | 147 |
153 SpeechInputExtensionInterface::~SpeechInputExtensionInterface() { | 148 SpeechInputExtensionInterface::~SpeechInputExtensionInterface() { |
154 } | 149 } |
155 | 150 |
156 SpeechInputExtensionManager::SpeechInputExtensionManager(Profile* profile) | 151 SpeechInputExtensionManager::SpeechInputExtensionManager(Profile* profile) |
157 : profile_(profile), | 152 : profile_(profile), |
158 state_(kIdle), | 153 state_(kIdle), |
159 registrar_(new content::NotificationRegistrar), | 154 registrar_(new content::NotificationRegistrar), |
160 speech_interface_(NULL) { | 155 speech_interface_(NULL), |
156 is_recognition_in_progress_(false), | |
157 speech_recognition_session_id_( | |
158 SpeechRecognitionManager::kSessionIDInvalid) { | |
161 registrar_->Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, | 159 registrar_->Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
162 content::Source<Profile>(profile_)); | 160 content::Source<Profile>(profile_)); |
163 } | 161 } |
164 | 162 |
165 SpeechInputExtensionManager::~SpeechInputExtensionManager() { | 163 SpeechInputExtensionManager::~SpeechInputExtensionManager() { |
166 } | 164 } |
167 | 165 |
168 SpeechInputExtensionManager* SpeechInputExtensionManager::GetForProfile( | 166 SpeechInputExtensionManager* SpeechInputExtensionManager::GetForProfile( |
169 Profile* profile) { | 167 Profile* profile) { |
170 SpeechInputExtensionManagerWrapper* wrapper = | 168 SpeechInputExtensionManagerWrapper* wrapper = |
(...skipping 16 matching lines...) Expand all Loading... | |
187 extension->id()); | 185 extension->id()); |
188 } else { | 186 } else { |
189 NOTREACHED(); | 187 NOTREACHED(); |
190 } | 188 } |
191 } | 189 } |
192 | 190 |
193 void SpeechInputExtensionManager::ShutdownOnUIThread() { | 191 void SpeechInputExtensionManager::ShutdownOnUIThread() { |
194 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 192 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
195 VLOG(1) << "Profile shutting down."; | 193 VLOG(1) << "Profile shutting down."; |
196 | 194 |
195 // Note: Unretained(this) is safe, also if we are freed in the meanwhile. | |
196 // It is used by the SR manager just for comparing the raw pointer and remove | |
197 // the associated sessions. | |
198 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
199 base::Bind(&SpeechInputExtensionManager::UnregisterFromManagerOnIOThread, | |
200 base::Unretained(this))); | |
201 | |
197 base::AutoLock auto_lock(state_lock_); | 202 base::AutoLock auto_lock(state_lock_); |
198 DCHECK(state_ != kShutdown); | 203 DCHECK(state_ != kShutdown); |
199 if (state_ != kIdle) { | 204 if (state_ != kIdle) { |
200 DCHECK(notification_.get()); | |
201 notification_->Hide(); | |
202 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | 205 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
203 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); | 206 base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this)); |
204 } | 207 } |
205 state_ = kShutdown; | 208 state_ = kShutdown; |
206 VLOG(1) << "Entering the shutdown sink state."; | 209 VLOG(1) << "Entering the shutdown sink state."; |
207 registrar_.reset(); | 210 registrar_.reset(); |
208 profile_ = NULL; | 211 profile_ = NULL; |
209 } | 212 } |
210 | 213 |
214 void SpeechInputExtensionManager::UnregisterFromManagerOnIOThread() { | |
Satish
2012/05/24 09:50:44
UnregisterFromManager is a bit confusing since thi
Primiano Tucci (use gerrit)
2012/05/24 10:14:07
Uh right. Done
| |
215 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
216 // TODO(primiano) The following check should not be really needed if the | |
217 // SpeechRecognitionManager and this class are destroyed in the correct order | |
218 // (this class first), as it is in current chrome implementation. | |
219 // However, it seems the some ChromiumOS tests violate the destruction order | |
220 // envisaged by browser_main_loop, causing a crash on this line. | |
Satish
2012/05/24 09:50:44
'causing a crash on this line' -> 'so SpeechRecogn
Primiano Tucci (use gerrit)
2012/05/24 10:14:07
Done.
| |
221 if (SpeechRecognitionManager* mgr = SpeechRecognitionManager::GetInstance()) | |
222 mgr->AbortAllSessionsForListener(this); | |
223 } | |
224 | |
211 void SpeechInputExtensionManager::ExtensionUnloaded( | 225 void SpeechInputExtensionManager::ExtensionUnloaded( |
212 const std::string& extension_id) { | 226 const std::string& extension_id) { |
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 227 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
214 | 228 |
215 base::AutoLock auto_lock(state_lock_); | 229 base::AutoLock auto_lock(state_lock_); |
216 if (state_ == kShutdown) | 230 if (state_ == kShutdown) |
217 return; | 231 return; |
218 | 232 |
219 VLOG(1) << "Extension unloaded. Requesting to enforce stop..."; | 233 VLOG(1) << "Extension unloaded. Requesting to enforce stop..."; |
220 if (extension_id_in_use_ == extension_id) { | 234 if (extension_id_in_use_ == extension_id) { |
(...skipping 17 matching lines...) Expand all Loading... | |
238 void SpeechInputExtensionManager::ResetToIdleState() { | 252 void SpeechInputExtensionManager::ResetToIdleState() { |
239 VLOG(1) << "State changed to idle. Deassociating any extensions."; | 253 VLOG(1) << "State changed to idle. Deassociating any extensions."; |
240 state_ = kIdle; | 254 state_ = kIdle; |
241 extension_id_in_use_.clear(); | 255 extension_id_in_use_.clear(); |
242 } | 256 } |
243 | 257 |
244 void SpeechInputExtensionManager::OnRecognitionResult( | 258 void SpeechInputExtensionManager::OnRecognitionResult( |
245 int session_id, | 259 int session_id, |
246 const content::SpeechRecognitionResult& result) { | 260 const content::SpeechRecognitionResult& result) { |
247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
248 DCHECK_EQ(session_id, kSpeechInputSessionId); | 262 DCHECK_EQ(session_id, speech_recognition_session_id_); |
249 | 263 |
250 // Stopping will start the disassociation with the extension. | 264 // Stopping will start the disassociation with the extension. |
251 // Make a copy to report the results to the proper one. | 265 // Make a copy to report the results to the proper one. |
252 std::string extension_id = extension_id_in_use_; | 266 std::string extension_id = extension_id_in_use_; |
253 ForceStopOnIOThread(); | 267 ForceStopOnIOThread(); |
254 | 268 |
255 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 269 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
256 base::Bind(&SpeechInputExtensionManager::SetRecognitionResultOnUIThread, | 270 base::Bind(&SpeechInputExtensionManager::SetRecognitionResultOnUIThread, |
257 this, result, extension_id)); | 271 this, result, extension_id)); |
258 } | 272 } |
(...skipping 22 matching lines...) Expand all Loading... | |
281 hypothesis.confidence); | 295 hypothesis.confidence); |
282 } | 296 } |
283 | 297 |
284 std::string json_args; | 298 std::string json_args; |
285 base::JSONWriter::Write(&args, &json_args); | 299 base::JSONWriter::Write(&args, &json_args); |
286 VLOG(1) << "Results: " << json_args; | 300 VLOG(1) << "Results: " << json_args; |
287 DispatchEventToExtension(extension_id, kOnResultEvent, json_args); | 301 DispatchEventToExtension(extension_id, kOnResultEvent, json_args); |
288 } | 302 } |
289 | 303 |
290 void SpeechInputExtensionManager::OnRecognitionStart(int session_id) { | 304 void SpeechInputExtensionManager::OnRecognitionStart(int session_id) { |
291 DCHECK_EQ(session_id, kSpeechInputSessionId); | 305 DCHECK_EQ(session_id, speech_recognition_session_id_); |
292 } | 306 } |
293 | 307 |
294 void SpeechInputExtensionManager::OnAudioStart(int session_id) { | 308 void SpeechInputExtensionManager::OnAudioStart(int session_id) { |
295 VLOG(1) << "OnAudioStart"; | 309 VLOG(1) << "OnAudioStart"; |
296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
297 DCHECK_EQ(session_id, kSpeechInputSessionId); | 311 DCHECK_EQ(session_id, speech_recognition_session_id_); |
298 | 312 |
299 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 313 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
300 base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread, | 314 base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread, |
301 this)); | 315 this)); |
302 } | 316 } |
303 | 317 |
304 void SpeechInputExtensionManager::OnAudioEnd(int session_id) { | 318 void SpeechInputExtensionManager::OnAudioEnd(int session_id) { |
305 DCHECK_EQ(session_id, kSpeechInputSessionId); | |
306 } | 319 } |
307 | 320 |
308 void SpeechInputExtensionManager::OnRecognitionEnd(int session_id) { | 321 void SpeechInputExtensionManager::OnRecognitionEnd(int session_id) { |
309 DCHECK_EQ(session_id, kSpeechInputSessionId); | 322 // In the very exceptional case in which we requested a new recognition before |
323 // the previous one ended, don't clobber the speech_recognition_session_id_. | |
324 if (speech_recognition_session_id_ == session_id) { | |
325 is_recognition_in_progress_ = false; | |
326 speech_recognition_session_id_ = | |
327 SpeechRecognitionManager::kSessionIDInvalid; | |
328 } | |
310 } | 329 } |
311 | 330 |
312 void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { | 331 void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { |
313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 332 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
314 | 333 |
315 base::AutoLock auto_lock(state_lock_); | 334 base::AutoLock auto_lock(state_lock_); |
316 if (state_ == kShutdown) | 335 if (state_ == kShutdown) |
317 return; | 336 return; |
318 | 337 |
319 DCHECK_EQ(state_, kStarting); | 338 DCHECK_EQ(state_, kStarting); |
320 VLOG(1) << "State changed to recording"; | 339 VLOG(1) << "State changed to recording"; |
321 state_ = kRecording; | 340 state_ = kRecording; |
322 | 341 |
323 const extensions::Extension* extension = profile_->GetExtensionService()-> | 342 DCHECK(profile_); |
324 GetExtensionById(extension_id_in_use_, true); | 343 profile_->GetPrefs()->SetBoolean( |
325 DCHECK(extension); | 344 prefs::kSpeechInputTrayNotificationShown, true); |
326 | |
327 bool show_notification = !profile_->GetPrefs()->GetBoolean( | |
328 prefs::kSpeechInputTrayNotificationShown); | |
329 | |
330 if (!notification_.get()) | |
331 notification_ = new SpeechRecognitionTrayIconController(); | |
332 notification_->Show(UTF8ToUTF16(extension->name()), show_notification); | |
333 | |
334 if (show_notification) { | |
335 profile_->GetPrefs()->SetBoolean( | |
336 prefs::kSpeechInputTrayNotificationShown, true); | |
337 } | |
338 | 345 |
339 VLOG(1) << "Sending start notification"; | 346 VLOG(1) << "Sending start notification"; |
340 content::NotificationService::current()->Notify( | 347 content::NotificationService::current()->Notify( |
341 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED, | 348 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED, |
342 content::Source<Profile>(profile_), | 349 content::Source<Profile>(profile_), |
343 content::Details<std::string>(&extension_id_in_use_)); | 350 content::Details<std::string>(&extension_id_in_use_)); |
344 } | 351 } |
345 | 352 |
346 void SpeechInputExtensionManager::OnRecognitionError( | 353 void SpeechInputExtensionManager::OnRecognitionError( |
347 int session_id, const content::SpeechRecognitionError& error) { | 354 int session_id, const content::SpeechRecognitionError& error) { |
348 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 355 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
349 DCHECK_EQ(session_id, kSpeechInputSessionId); | 356 DCHECK_EQ(session_id, speech_recognition_session_id_); |
350 | |
351 // Simply return in case of an ERROR_ABORTED, since it is not contemplated | |
352 // in the speech input extensions architecture. | |
353 if (error.code == content::SPEECH_RECOGNITION_ERROR_ABORTED) | |
354 return; | |
355 | |
356 VLOG(1) << "OnRecognitionError: " << error.code; | 357 VLOG(1) << "OnRecognitionError: " << error.code; |
357 | 358 |
358 base::AutoLock auto_lock(state_lock_); | 359 base::AutoLock auto_lock(state_lock_); |
359 if (state_ == kShutdown) | 360 if (state_ == kShutdown) |
360 return; | 361 return; |
361 | 362 |
362 // Release the recognizer object. | |
363 GetSpeechInputExtensionInterface()->StopRecording(true); | 363 GetSpeechInputExtensionInterface()->StopRecording(true); |
364 | 364 |
365 std::string event_error_code; | 365 std::string event_error_code; |
366 bool report_to_event = true; | 366 bool report_to_event = true; |
367 | 367 |
368 switch (error.code) { | 368 switch (error.code) { |
369 case content::SPEECH_RECOGNITION_ERROR_NONE: | 369 case content::SPEECH_RECOGNITION_ERROR_NONE: |
370 break; | 370 break; |
371 | 371 |
372 case content::SPEECH_RECOGNITION_ERROR_ABORTED: | |
373 // ERROR_ABORTED is received whenever AbortSession is called on the | |
374 // manager. However, we want propagate the error only if it is triggered | |
375 // by an external cause (another recognition started, aborting us), thus | |
376 // only if it occurs while we are capturing audio. | |
377 if (state_ == kRecording) | |
378 event_error_code = kErrorCaptureError; | |
379 break; | |
380 | |
372 case content::SPEECH_RECOGNITION_ERROR_AUDIO: | 381 case content::SPEECH_RECOGNITION_ERROR_AUDIO: |
373 if (state_ == kStarting) { | 382 if (state_ == kStarting) { |
374 event_error_code = kErrorUnableToStart; | 383 event_error_code = kErrorUnableToStart; |
375 report_to_event = false; | 384 report_to_event = false; |
376 } else { | 385 } else { |
377 event_error_code = kErrorCaptureError; | 386 event_error_code = kErrorCaptureError; |
378 } | 387 } |
379 break; | 388 break; |
380 | 389 |
381 case content::SPEECH_RECOGNITION_ERROR_NETWORK: | 390 case content::SPEECH_RECOGNITION_ERROR_NETWORK: |
(...skipping 22 matching lines...) Expand all Loading... | |
404 | 413 |
405 if (!event_error_code.empty()) { | 414 if (!event_error_code.empty()) { |
406 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 415 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
407 base::Bind(&SpeechInputExtensionManager::DispatchError, | 416 base::Bind(&SpeechInputExtensionManager::DispatchError, |
408 this, event_error_code, report_to_event)); | 417 this, event_error_code, report_to_event)); |
409 } | 418 } |
410 } | 419 } |
411 | 420 |
412 void SpeechInputExtensionManager::OnEnvironmentEstimationComplete( | 421 void SpeechInputExtensionManager::OnEnvironmentEstimationComplete( |
413 int session_id) { | 422 int session_id) { |
414 DCHECK_EQ(session_id, kSpeechInputSessionId); | 423 DCHECK_EQ(session_id, speech_recognition_session_id_); |
415 } | 424 } |
416 | 425 |
417 void SpeechInputExtensionManager::OnSoundStart(int session_id) { | 426 void SpeechInputExtensionManager::OnSoundStart(int session_id) { |
418 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 427 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
419 DCHECK_EQ(session_id, kSpeechInputSessionId); | 428 DCHECK_EQ(session_id, speech_recognition_session_id_); |
420 VLOG(1) << "OnSoundStart"; | 429 VLOG(1) << "OnSoundStart"; |
421 | 430 |
422 std::string json_args; | 431 std::string json_args; |
423 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 432 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
424 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, | 433 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, |
425 this, extension_id_in_use_, std::string(kOnSoundStartEvent), | 434 this, extension_id_in_use_, std::string(kOnSoundStartEvent), |
426 json_args)); | 435 json_args)); |
427 } | 436 } |
428 | 437 |
429 void SpeechInputExtensionManager::OnSoundEnd(int session_id) { | 438 void SpeechInputExtensionManager::OnSoundEnd(int session_id) { |
430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
431 DCHECK_EQ(session_id, kSpeechInputSessionId); | |
432 VLOG(1) << "OnSoundEnd"; | 440 VLOG(1) << "OnSoundEnd"; |
433 | 441 |
434 std::string json_args; | 442 std::string json_args; |
435 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 443 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
436 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, | 444 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, |
437 this, extension_id_in_use_, std::string(kOnSoundEndEvent), | 445 this, extension_id_in_use_, std::string(kOnSoundEndEvent), |
438 json_args)); | 446 json_args)); |
439 } | 447 } |
440 | 448 |
441 void SpeechInputExtensionManager::DispatchEventToExtension( | 449 void SpeechInputExtensionManager::DispatchEventToExtension( |
(...skipping 22 matching lines...) Expand all Loading... | |
464 void SpeechInputExtensionManager::DispatchError( | 472 void SpeechInputExtensionManager::DispatchError( |
465 const std::string& error, bool dispatch_event) { | 473 const std::string& error, bool dispatch_event) { |
466 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 474 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
467 | 475 |
468 std::string extension_id; | 476 std::string extension_id; |
469 { | 477 { |
470 base::AutoLock auto_lock(state_lock_); | 478 base::AutoLock auto_lock(state_lock_); |
471 if (state_ == kShutdown) | 479 if (state_ == kShutdown) |
472 return; | 480 return; |
473 | 481 |
474 if (state_ == kRecording) { | |
475 DCHECK(notification_.get()); | |
476 notification_->Hide(); | |
477 } | |
478 | |
479 extension_id = extension_id_in_use_; | 482 extension_id = extension_id_in_use_; |
480 ResetToIdleState(); | 483 ResetToIdleState(); |
481 | 484 |
482 // Will set the error property in the ongoing extension function calls. | 485 // Will set the error property in the ongoing extension function calls. |
483 ExtensionError details(extension_id, error); | 486 ExtensionError details(extension_id, error); |
484 content::NotificationService::current()->Notify( | 487 content::NotificationService::current()->Notify( |
485 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED, | 488 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED, |
486 content::Source<Profile>(profile_), | 489 content::Source<Profile>(profile_), |
487 content::Details<ExtensionError>(&details)); | 490 content::Details<ExtensionError>(&details)); |
488 } | 491 } |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
524 | 527 |
525 case kRecording: | 528 case kRecording: |
526 case kStopping: | 529 case kStopping: |
527 *error = kErrorInvalidOperation; | 530 *error = kErrorInvalidOperation; |
528 return false; | 531 return false; |
529 | 532 |
530 default: | 533 default: |
531 NOTREACHED(); | 534 NOTREACHED(); |
532 } | 535 } |
533 | 536 |
537 const extensions::Extension* extension = profile_->GetExtensionService()-> | |
538 GetExtensionById(extension_id, true); | |
539 DCHECK(extension); | |
540 const string16& extension_name = UTF8ToUTF16(extension->name()); | |
541 | |
534 extension_id_in_use_ = extension_id; | 542 extension_id_in_use_ = extension_id; |
535 VLOG(1) << "State changed to starting"; | 543 VLOG(1) << "State changed to starting"; |
536 state_ = kStarting; | 544 state_ = kStarting; |
537 | 545 |
546 // Checks if the security notification balloon has been already shown (only | |
547 // once for a profile). It is reset on DidStartReceivingAudioOnUIThread. | |
548 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter = | |
549 profile_->GetRequestContext(); | |
550 const bool show_notification = !profile_->GetPrefs()->GetBoolean( | |
551 prefs::kSpeechInputTrayNotificationShown); | |
552 | |
538 BrowserThread::PostTask( | 553 BrowserThread::PostTask( |
539 BrowserThread::IO, FROM_HERE, | 554 BrowserThread::IO, FROM_HERE, |
540 base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this, | 555 base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this, |
541 profile_->GetRequestContext(), language, grammar, | 556 url_request_context_getter, extension_name, language, grammar, |
542 filter_profanities)); | 557 filter_profanities, show_notification)); |
543 return true; | 558 return true; |
544 } | 559 } |
545 | 560 |
546 void SpeechInputExtensionManager::StartOnIOThread( | 561 void SpeechInputExtensionManager::StartOnIOThread( |
547 net::URLRequestContextGetter* context_getter, | 562 scoped_refptr<net::URLRequestContextGetter> context_getter, |
563 const string16& extension_name, | |
548 const std::string& language, | 564 const std::string& language, |
549 const std::string& grammar, | 565 const std::string& grammar, |
550 bool filter_profanities) { | 566 bool filter_profanities, |
567 bool show_notification) { | |
551 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
552 VLOG(1) << "Requesting start (IO thread)"; | 569 VLOG(1) << "Requesting start (IO thread)"; |
553 | 570 |
554 // Everything put inside the lock to ensure the validity of context_getter, | 571 // Everything put inside the lock to ensure the validity of context_getter, |
555 // guaranteed while not in the shutdown state. Any ongoing or recognition | 572 // guaranteed while not in the shutdown state. Any ongoing or recognition |
556 // request will be requested to be aborted when entering the shutdown state. | 573 // request will be requested to be aborted when entering the shutdown state. |
557 base::AutoLock auto_lock(state_lock_); | 574 base::AutoLock auto_lock(state_lock_); |
558 if (state_ == kShutdown) | 575 if (state_ == kShutdown) |
559 return; | 576 return; |
560 | 577 |
578 // TODO(primiano) These two checks below could be avoided, since they are | |
579 // already handled in the speech recognition classes. However, since the | |
580 // speech input extensions tests are bypassing the manager, we need them to | |
581 // pass the tests. A complete unit test which puts all the pieces together, | |
582 // mocking just the endpoints (the audio input controller and the URL fetcher) | |
583 // should be written. | |
561 if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) { | 584 if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) { |
562 BrowserThread::PostTask( | 585 BrowserThread::PostTask( |
563 BrowserThread::UI, FROM_HERE, | 586 BrowserThread::UI, FROM_HERE, |
564 base::Bind(&SpeechInputExtensionManager::DispatchError, this, | 587 base::Bind(&SpeechInputExtensionManager::DispatchError, this, |
565 std::string(kErrorNoRecordingDeviceFound), false)); | 588 std::string(kErrorNoRecordingDeviceFound), false)); |
566 return; | 589 return; |
567 } | 590 } |
568 | 591 |
569 if (GetSpeechInputExtensionInterface()->IsCapturingAudio()) { | 592 if (GetSpeechInputExtensionInterface()->IsCapturingAudio()) { |
570 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 593 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
571 base::Bind(&SpeechInputExtensionManager::DispatchError, this, | 594 base::Bind(&SpeechInputExtensionManager::DispatchError, this, |
572 std::string(kErrorRecordingDeviceInUse), false)); | 595 std::string(kErrorRecordingDeviceInUse), false)); |
573 return; | 596 return; |
574 } | 597 } |
575 | 598 |
576 GetSpeechInputExtensionInterface()->StartRecording( | 599 GetSpeechInputExtensionInterface()->StartRecording(this, |
577 this, context_getter, kSpeechInputSessionId, language, grammar, | 600 context_getter, |
578 filter_profanities); | 601 extension_name, |
602 language, | |
603 grammar, | |
604 filter_profanities, | |
605 show_notification); | |
579 } | 606 } |
580 | 607 |
581 bool SpeechInputExtensionManager::HasAudioInputDevices() { | 608 bool SpeechInputExtensionManager::HasAudioInputDevices() { |
582 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 609 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
583 return SpeechRecognitionManager::GetInstance()->HasAudioInputDevices(); | 610 return SpeechRecognitionManager::GetInstance()->HasAudioInputDevices(); |
584 } | 611 } |
585 | 612 |
586 bool SpeechInputExtensionManager::IsCapturingAudio() { | 613 bool SpeechInputExtensionManager::IsCapturingAudio() { |
587 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 614 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
588 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); | 615 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); |
589 } | 616 } |
590 | 617 |
591 void SpeechInputExtensionManager::IsRecording( | 618 void SpeechInputExtensionManager::IsRecording( |
592 const IsRecordingCallback& callback) { | 619 const IsRecordingCallback& callback) { |
593 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 620 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
594 BrowserThread::PostTask( | 621 BrowserThread::PostTask( |
595 BrowserThread::IO, FROM_HERE, | 622 BrowserThread::IO, FROM_HERE, |
596 base::Bind(&SpeechInputExtensionManager::IsRecordingOnIOThread, | 623 base::Bind(&SpeechInputExtensionManager::IsRecordingOnIOThread, |
597 this, callback)); | 624 this, callback)); |
598 } | 625 } |
599 | 626 |
600 void SpeechInputExtensionManager::IsRecordingOnIOThread( | 627 void SpeechInputExtensionManager::IsRecordingOnIOThread( |
601 const IsRecordingCallback& callback) { | 628 const IsRecordingCallback& callback) { |
602 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 629 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
603 | 630 |
604 bool result = GetSpeechInputExtensionInterface()->IsCapturingAudio(); | 631 bool result = GetSpeechInputExtensionInterface()->IsCapturingAudio(); |
632 | |
605 BrowserThread::PostTask( | 633 BrowserThread::PostTask( |
606 BrowserThread::UI, FROM_HERE, | 634 BrowserThread::UI, FROM_HERE, |
607 base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread, | 635 base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread, |
608 this, callback, result)); | 636 this, callback, result)); |
609 } | 637 } |
610 | 638 |
611 void SpeechInputExtensionManager::IsRecordingOnUIThread( | 639 void SpeechInputExtensionManager::IsRecordingOnUIThread( |
612 const IsRecordingCallback& callback, | 640 const IsRecordingCallback& callback, |
613 bool result) { | 641 bool result) { |
614 BrowserThread::CurrentlyOn(BrowserThread::UI); | 642 BrowserThread::CurrentlyOn(BrowserThread::UI); |
615 callback.Run(result); | 643 callback.Run(result); |
616 } | 644 } |
617 | 645 |
618 void SpeechInputExtensionManager::StartRecording( | 646 void SpeechInputExtensionManager::StartRecording( |
619 content::SpeechRecognitionEventListener* listener, | 647 content::SpeechRecognitionEventListener* listener, |
620 net::URLRequestContextGetter* context_getter, | 648 net::URLRequestContextGetter* context_getter, |
621 int session_id, | 649 const string16& extension_name, |
622 const std::string& language, | 650 const std::string& language, |
623 const std::string& grammar, | 651 const std::string& grammar, |
624 bool filter_profanities) { | 652 bool filter_profanities, |
653 bool show_notification) { | |
625 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 654 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
626 DCHECK(!recognizer_); | 655 |
627 recognizer_ = content::SpeechRecognizer::Create( | 656 content::SpeechRecognitionSessionContext context; |
628 listener, session_id, language, grammar, context_getter, | 657 context.requested_by_page_element = false; |
629 filter_profanities, "", ""); | 658 context.is_first_request_for_context = show_notification; |
630 recognizer_->StartRecognition(); | 659 context.context_name = extension_name; |
660 | |
661 content::SpeechRecognitionSessionConfig config; | |
662 // config.is_one_shot = true; // TODO(primiano) Uncomment when CL2.0 lands. | |
663 config.language = language; | |
664 config.grammars.push_back(content::SpeechRecognitionGrammar(grammar)); | |
665 config.initial_context = context; | |
666 config.url_request_context_getter = context_getter; | |
667 config.filter_profanities = filter_profanities; | |
668 config.event_listener = listener; | |
669 | |
670 DCHECK(!is_recognition_in_progress_); | |
671 SpeechRecognitionManager& manager = *SpeechRecognitionManager::GetInstance(); | |
672 speech_recognition_session_id_ = | |
673 manager.CreateSession(config); | |
674 DCHECK_NE(speech_recognition_session_id_, | |
675 SpeechRecognitionManager::kSessionIDInvalid); | |
676 is_recognition_in_progress_ = true; | |
677 manager.StartSession(speech_recognition_session_id_); | |
631 } | 678 } |
632 | 679 |
633 bool SpeechInputExtensionManager::HasValidRecognizer() { | 680 bool SpeechInputExtensionManager::HasValidRecognizer() { |
634 return !!recognizer_; | 681 if (!is_recognition_in_progress_) |
682 return false; | |
683 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); | |
635 } | 684 } |
636 | 685 |
637 bool SpeechInputExtensionManager::Stop(const std::string& extension_id, | 686 bool SpeechInputExtensionManager::Stop(const std::string& extension_id, |
638 std::string* error) { | 687 std::string* error) { |
639 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 688 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
640 DCHECK(error); | 689 DCHECK(error); |
641 VLOG(1) << "Requesting stop (UI thread)"; | 690 VLOG(1) << "Requesting stop (UI thread)"; |
642 | 691 |
643 base::AutoLock auto_lock(state_lock_); | 692 base::AutoLock auto_lock(state_lock_); |
644 if (state_ == kShutdown || | 693 if (state_ == kShutdown || |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
685 GetSpeechInputExtensionInterface()->StopRecording(false); | 734 GetSpeechInputExtensionInterface()->StopRecording(false); |
686 | 735 |
687 if (state_ == kShutdown) | 736 if (state_ == kShutdown) |
688 return; | 737 return; |
689 | 738 |
690 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 739 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
691 base::Bind(&SpeechInputExtensionManager::StopSucceededOnUIThread, this)); | 740 base::Bind(&SpeechInputExtensionManager::StopSucceededOnUIThread, this)); |
692 } | 741 } |
693 | 742 |
694 void SpeechInputExtensionManager::StopRecording(bool recognition_failed) { | 743 void SpeechInputExtensionManager::StopRecording(bool recognition_failed) { |
695 if (recognizer_) { | 744 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
696 // Recognition is already cancelled in case of failure. | 745 if (!is_recognition_in_progress_) |
697 // Double-cancelling leads to assertion failures. | 746 return; |
698 if (!recognition_failed) | 747 DCHECK_NE(speech_recognition_session_id_, |
699 recognizer_->AbortRecognition(); | 748 SpeechRecognitionManager::kSessionIDInvalid); |
700 recognizer_.release(); | 749 SpeechRecognitionManager::GetInstance()->AbortSession( |
701 } | 750 speech_recognition_session_id_); |
702 } | 751 } |
703 | 752 |
704 void SpeechInputExtensionManager::StopSucceededOnUIThread() { | 753 void SpeechInputExtensionManager::StopSucceededOnUIThread() { |
705 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 754 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
706 VLOG(1) << "Stop succeeded (UI thread)"; | 755 VLOG(1) << "Stop succeeded (UI thread)"; |
707 | 756 |
708 base::AutoLock auto_lock(state_lock_); | 757 base::AutoLock auto_lock(state_lock_); |
709 if (state_ == kShutdown) | 758 if (state_ == kShutdown) |
710 return; | 759 return; |
711 | 760 |
712 std::string extension_id = extension_id_in_use_; | 761 std::string extension_id = extension_id_in_use_; |
713 ResetToIdleState(); | 762 ResetToIdleState(); |
714 | 763 |
715 DCHECK(notification_.get()); | |
716 notification_->Hide(); | |
717 | |
718 content::NotificationService::current()->Notify( | 764 content::NotificationService::current()->Notify( |
719 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED, | 765 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED, |
720 // Guarded by the state_ == kShutdown check. | 766 // Guarded by the state_ == kShutdown check. |
721 content::Source<Profile>(profile_), | 767 content::Source<Profile>(profile_), |
722 content::Details<std::string>(&extension_id)); | 768 content::Details<std::string>(&extension_id)); |
723 } | 769 } |
724 | 770 |
725 void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id, | 771 void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id, |
726 float volume, | 772 float volume, |
727 float noise_volume) { | 773 float noise_volume) {} |
728 DCHECK_EQ(session_id, kSpeechInputSessionId); | |
729 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
730 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
731 base::Bind(&SpeechInputExtensionManager::SetInputVolumeOnUIThread, | |
732 this, volume)); | |
733 } | |
734 | |
735 void SpeechInputExtensionManager::SetInputVolumeOnUIThread( | |
736 float volume) { | |
737 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
738 DCHECK(notification_.get()); | |
739 notification_->SetVUMeterVolume(volume); | |
740 } | |
OLD | NEW |