| 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::AbortAllSessionsOnIOThread, |
| 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::AbortAllSessionsOnIOThread() { |
| 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, so SpeechRecognitionmanager could have been |
| 221 // freed by now. |
| 222 if (SpeechRecognitionManager* mgr = SpeechRecognitionManager::GetInstance()) |
| 223 mgr->AbortAllSessionsForListener(this); |
| 224 } |
| 225 |
| 211 void SpeechInputExtensionManager::ExtensionUnloaded( | 226 void SpeechInputExtensionManager::ExtensionUnloaded( |
| 212 const std::string& extension_id) { | 227 const std::string& extension_id) { |
| 213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 214 | 229 |
| 215 base::AutoLock auto_lock(state_lock_); | 230 base::AutoLock auto_lock(state_lock_); |
| 216 if (state_ == kShutdown) | 231 if (state_ == kShutdown) |
| 217 return; | 232 return; |
| 218 | 233 |
| 219 VLOG(1) << "Extension unloaded. Requesting to enforce stop..."; | 234 VLOG(1) << "Extension unloaded. Requesting to enforce stop..."; |
| 220 if (extension_id_in_use_ == extension_id) { | 235 if (extension_id_in_use_ == extension_id) { |
| (...skipping 17 matching lines...) Expand all Loading... |
| 238 void SpeechInputExtensionManager::ResetToIdleState() { | 253 void SpeechInputExtensionManager::ResetToIdleState() { |
| 239 VLOG(1) << "State changed to idle. Deassociating any extensions."; | 254 VLOG(1) << "State changed to idle. Deassociating any extensions."; |
| 240 state_ = kIdle; | 255 state_ = kIdle; |
| 241 extension_id_in_use_.clear(); | 256 extension_id_in_use_.clear(); |
| 242 } | 257 } |
| 243 | 258 |
| 244 void SpeechInputExtensionManager::OnRecognitionResult( | 259 void SpeechInputExtensionManager::OnRecognitionResult( |
| 245 int session_id, | 260 int session_id, |
| 246 const content::SpeechRecognitionResult& result) { | 261 const content::SpeechRecognitionResult& result) { |
| 247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 262 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 248 DCHECK_EQ(session_id, kSpeechInputSessionId); | 263 DCHECK_EQ(session_id, speech_recognition_session_id_); |
| 249 | 264 |
| 250 // Stopping will start the disassociation with the extension. | 265 // Stopping will start the disassociation with the extension. |
| 251 // Make a copy to report the results to the proper one. | 266 // Make a copy to report the results to the proper one. |
| 252 std::string extension_id = extension_id_in_use_; | 267 std::string extension_id = extension_id_in_use_; |
| 253 ForceStopOnIOThread(); | 268 ForceStopOnIOThread(); |
| 254 | 269 |
| 255 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 270 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 256 base::Bind(&SpeechInputExtensionManager::SetRecognitionResultOnUIThread, | 271 base::Bind(&SpeechInputExtensionManager::SetRecognitionResultOnUIThread, |
| 257 this, result, extension_id)); | 272 this, result, extension_id)); |
| 258 } | 273 } |
| (...skipping 22 matching lines...) Expand all Loading... |
| 281 hypothesis.confidence); | 296 hypothesis.confidence); |
| 282 } | 297 } |
| 283 | 298 |
| 284 std::string json_args; | 299 std::string json_args; |
| 285 base::JSONWriter::Write(&args, &json_args); | 300 base::JSONWriter::Write(&args, &json_args); |
| 286 VLOG(1) << "Results: " << json_args; | 301 VLOG(1) << "Results: " << json_args; |
| 287 DispatchEventToExtension(extension_id, kOnResultEvent, json_args); | 302 DispatchEventToExtension(extension_id, kOnResultEvent, json_args); |
| 288 } | 303 } |
| 289 | 304 |
| 290 void SpeechInputExtensionManager::OnRecognitionStart(int session_id) { | 305 void SpeechInputExtensionManager::OnRecognitionStart(int session_id) { |
| 291 DCHECK_EQ(session_id, kSpeechInputSessionId); | 306 DCHECK_EQ(session_id, speech_recognition_session_id_); |
| 292 } | 307 } |
| 293 | 308 |
| 294 void SpeechInputExtensionManager::OnAudioStart(int session_id) { | 309 void SpeechInputExtensionManager::OnAudioStart(int session_id) { |
| 295 VLOG(1) << "OnAudioStart"; | 310 VLOG(1) << "OnAudioStart"; |
| 296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 297 DCHECK_EQ(session_id, kSpeechInputSessionId); | 312 DCHECK_EQ(session_id, speech_recognition_session_id_); |
| 298 | 313 |
| 299 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 314 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 300 base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread, | 315 base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread, |
| 301 this)); | 316 this)); |
| 302 } | 317 } |
| 303 | 318 |
| 304 void SpeechInputExtensionManager::OnAudioEnd(int session_id) { | 319 void SpeechInputExtensionManager::OnAudioEnd(int session_id) { |
| 305 DCHECK_EQ(session_id, kSpeechInputSessionId); | |
| 306 } | 320 } |
| 307 | 321 |
| 308 void SpeechInputExtensionManager::OnRecognitionEnd(int session_id) { | 322 void SpeechInputExtensionManager::OnRecognitionEnd(int session_id) { |
| 309 DCHECK_EQ(session_id, kSpeechInputSessionId); | 323 // In the very exceptional case in which we requested a new recognition before |
| 324 // the previous one ended, don't clobber the speech_recognition_session_id_. |
| 325 if (speech_recognition_session_id_ == session_id) { |
| 326 is_recognition_in_progress_ = false; |
| 327 speech_recognition_session_id_ = |
| 328 SpeechRecognitionManager::kSessionIDInvalid; |
| 329 } |
| 310 } | 330 } |
| 311 | 331 |
| 312 void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { | 332 void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() { |
| 313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 314 | 334 |
| 315 base::AutoLock auto_lock(state_lock_); | 335 base::AutoLock auto_lock(state_lock_); |
| 316 if (state_ == kShutdown) | 336 if (state_ == kShutdown) |
| 317 return; | 337 return; |
| 318 | 338 |
| 319 DCHECK_EQ(state_, kStarting); | 339 DCHECK_EQ(state_, kStarting); |
| 320 VLOG(1) << "State changed to recording"; | 340 VLOG(1) << "State changed to recording"; |
| 321 state_ = kRecording; | 341 state_ = kRecording; |
| 322 | 342 |
| 323 const extensions::Extension* extension = profile_->GetExtensionService()-> | 343 DCHECK(profile_); |
| 324 GetExtensionById(extension_id_in_use_, true); | 344 profile_->GetPrefs()->SetBoolean( |
| 325 DCHECK(extension); | 345 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 | 346 |
| 339 VLOG(1) << "Sending start notification"; | 347 VLOG(1) << "Sending start notification"; |
| 340 content::NotificationService::current()->Notify( | 348 content::NotificationService::current()->Notify( |
| 341 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED, | 349 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED, |
| 342 content::Source<Profile>(profile_), | 350 content::Source<Profile>(profile_), |
| 343 content::Details<std::string>(&extension_id_in_use_)); | 351 content::Details<std::string>(&extension_id_in_use_)); |
| 344 } | 352 } |
| 345 | 353 |
| 346 void SpeechInputExtensionManager::OnRecognitionError( | 354 void SpeechInputExtensionManager::OnRecognitionError( |
| 347 int session_id, const content::SpeechRecognitionError& error) { | 355 int session_id, const content::SpeechRecognitionError& error) { |
| 348 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 356 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 349 DCHECK_EQ(session_id, kSpeechInputSessionId); | 357 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; | 358 VLOG(1) << "OnRecognitionError: " << error.code; |
| 357 | 359 |
| 358 base::AutoLock auto_lock(state_lock_); | 360 base::AutoLock auto_lock(state_lock_); |
| 359 if (state_ == kShutdown) | 361 if (state_ == kShutdown) |
| 360 return; | 362 return; |
| 361 | 363 |
| 362 // Release the recognizer object. | |
| 363 GetSpeechInputExtensionInterface()->StopRecording(true); | 364 GetSpeechInputExtensionInterface()->StopRecording(true); |
| 364 | 365 |
| 365 std::string event_error_code; | 366 std::string event_error_code; |
| 366 bool report_to_event = true; | 367 bool report_to_event = true; |
| 367 | 368 |
| 368 switch (error.code) { | 369 switch (error.code) { |
| 369 case content::SPEECH_RECOGNITION_ERROR_NONE: | 370 case content::SPEECH_RECOGNITION_ERROR_NONE: |
| 370 break; | 371 break; |
| 371 | 372 |
| 373 case content::SPEECH_RECOGNITION_ERROR_ABORTED: |
| 374 // ERROR_ABORTED is received whenever AbortSession is called on the |
| 375 // manager. However, we want propagate the error only if it is triggered |
| 376 // by an external cause (another recognition started, aborting us), thus |
| 377 // only if it occurs while we are capturing audio. |
| 378 if (state_ == kRecording) |
| 379 event_error_code = kErrorCaptureError; |
| 380 break; |
| 381 |
| 372 case content::SPEECH_RECOGNITION_ERROR_AUDIO: | 382 case content::SPEECH_RECOGNITION_ERROR_AUDIO: |
| 373 if (state_ == kStarting) { | 383 if (state_ == kStarting) { |
| 374 event_error_code = kErrorUnableToStart; | 384 event_error_code = kErrorUnableToStart; |
| 375 report_to_event = false; | 385 report_to_event = false; |
| 376 } else { | 386 } else { |
| 377 event_error_code = kErrorCaptureError; | 387 event_error_code = kErrorCaptureError; |
| 378 } | 388 } |
| 379 break; | 389 break; |
| 380 | 390 |
| 381 case content::SPEECH_RECOGNITION_ERROR_NETWORK: | 391 case content::SPEECH_RECOGNITION_ERROR_NETWORK: |
| (...skipping 22 matching lines...) Expand all Loading... |
| 404 | 414 |
| 405 if (!event_error_code.empty()) { | 415 if (!event_error_code.empty()) { |
| 406 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 416 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 407 base::Bind(&SpeechInputExtensionManager::DispatchError, | 417 base::Bind(&SpeechInputExtensionManager::DispatchError, |
| 408 this, event_error_code, report_to_event)); | 418 this, event_error_code, report_to_event)); |
| 409 } | 419 } |
| 410 } | 420 } |
| 411 | 421 |
| 412 void SpeechInputExtensionManager::OnEnvironmentEstimationComplete( | 422 void SpeechInputExtensionManager::OnEnvironmentEstimationComplete( |
| 413 int session_id) { | 423 int session_id) { |
| 414 DCHECK_EQ(session_id, kSpeechInputSessionId); | 424 DCHECK_EQ(session_id, speech_recognition_session_id_); |
| 415 } | 425 } |
| 416 | 426 |
| 417 void SpeechInputExtensionManager::OnSoundStart(int session_id) { | 427 void SpeechInputExtensionManager::OnSoundStart(int session_id) { |
| 418 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 419 DCHECK_EQ(session_id, kSpeechInputSessionId); | 429 DCHECK_EQ(session_id, speech_recognition_session_id_); |
| 420 VLOG(1) << "OnSoundStart"; | 430 VLOG(1) << "OnSoundStart"; |
| 421 | 431 |
| 422 std::string json_args; | 432 std::string json_args; |
| 423 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 433 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 424 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, | 434 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, |
| 425 this, extension_id_in_use_, std::string(kOnSoundStartEvent), | 435 this, extension_id_in_use_, std::string(kOnSoundStartEvent), |
| 426 json_args)); | 436 json_args)); |
| 427 } | 437 } |
| 428 | 438 |
| 429 void SpeechInputExtensionManager::OnSoundEnd(int session_id) { | 439 void SpeechInputExtensionManager::OnSoundEnd(int session_id) { |
| 430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 440 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 431 DCHECK_EQ(session_id, kSpeechInputSessionId); | |
| 432 VLOG(1) << "OnSoundEnd"; | 441 VLOG(1) << "OnSoundEnd"; |
| 433 | 442 |
| 434 std::string json_args; | 443 std::string json_args; |
| 435 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 444 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 436 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, | 445 base::Bind(&SpeechInputExtensionManager::DispatchEventToExtension, |
| 437 this, extension_id_in_use_, std::string(kOnSoundEndEvent), | 446 this, extension_id_in_use_, std::string(kOnSoundEndEvent), |
| 438 json_args)); | 447 json_args)); |
| 439 } | 448 } |
| 440 | 449 |
| 441 void SpeechInputExtensionManager::DispatchEventToExtension( | 450 void SpeechInputExtensionManager::DispatchEventToExtension( |
| (...skipping 22 matching lines...) Expand all Loading... |
| 464 void SpeechInputExtensionManager::DispatchError( | 473 void SpeechInputExtensionManager::DispatchError( |
| 465 const std::string& error, bool dispatch_event) { | 474 const std::string& error, bool dispatch_event) { |
| 466 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 475 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 467 | 476 |
| 468 std::string extension_id; | 477 std::string extension_id; |
| 469 { | 478 { |
| 470 base::AutoLock auto_lock(state_lock_); | 479 base::AutoLock auto_lock(state_lock_); |
| 471 if (state_ == kShutdown) | 480 if (state_ == kShutdown) |
| 472 return; | 481 return; |
| 473 | 482 |
| 474 if (state_ == kRecording) { | |
| 475 DCHECK(notification_.get()); | |
| 476 notification_->Hide(); | |
| 477 } | |
| 478 | |
| 479 extension_id = extension_id_in_use_; | 483 extension_id = extension_id_in_use_; |
| 480 ResetToIdleState(); | 484 ResetToIdleState(); |
| 481 | 485 |
| 482 // Will set the error property in the ongoing extension function calls. | 486 // Will set the error property in the ongoing extension function calls. |
| 483 ExtensionError details(extension_id, error); | 487 ExtensionError details(extension_id, error); |
| 484 content::NotificationService::current()->Notify( | 488 content::NotificationService::current()->Notify( |
| 485 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED, | 489 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED, |
| 486 content::Source<Profile>(profile_), | 490 content::Source<Profile>(profile_), |
| 487 content::Details<ExtensionError>(&details)); | 491 content::Details<ExtensionError>(&details)); |
| 488 } | 492 } |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 524 | 528 |
| 525 case kRecording: | 529 case kRecording: |
| 526 case kStopping: | 530 case kStopping: |
| 527 *error = kErrorInvalidOperation; | 531 *error = kErrorInvalidOperation; |
| 528 return false; | 532 return false; |
| 529 | 533 |
| 530 default: | 534 default: |
| 531 NOTREACHED(); | 535 NOTREACHED(); |
| 532 } | 536 } |
| 533 | 537 |
| 538 const extensions::Extension* extension = profile_->GetExtensionService()-> |
| 539 GetExtensionById(extension_id, true); |
| 540 DCHECK(extension); |
| 541 const string16& extension_name = UTF8ToUTF16(extension->name()); |
| 542 |
| 534 extension_id_in_use_ = extension_id; | 543 extension_id_in_use_ = extension_id; |
| 535 VLOG(1) << "State changed to starting"; | 544 VLOG(1) << "State changed to starting"; |
| 536 state_ = kStarting; | 545 state_ = kStarting; |
| 537 | 546 |
| 547 // Checks if the security notification balloon has been already shown (only |
| 548 // once for a profile). It is reset on DidStartReceivingAudioOnUIThread. |
| 549 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter = |
| 550 profile_->GetRequestContext(); |
| 551 const bool show_notification = !profile_->GetPrefs()->GetBoolean( |
| 552 prefs::kSpeechInputTrayNotificationShown); |
| 553 |
| 538 BrowserThread::PostTask( | 554 BrowserThread::PostTask( |
| 539 BrowserThread::IO, FROM_HERE, | 555 BrowserThread::IO, FROM_HERE, |
| 540 base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this, | 556 base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this, |
| 541 profile_->GetRequestContext(), language, grammar, | 557 url_request_context_getter, extension_name, language, grammar, |
| 542 filter_profanities)); | 558 filter_profanities, show_notification)); |
| 543 return true; | 559 return true; |
| 544 } | 560 } |
| 545 | 561 |
| 546 void SpeechInputExtensionManager::StartOnIOThread( | 562 void SpeechInputExtensionManager::StartOnIOThread( |
| 547 net::URLRequestContextGetter* context_getter, | 563 scoped_refptr<net::URLRequestContextGetter> context_getter, |
| 564 const string16& extension_name, |
| 548 const std::string& language, | 565 const std::string& language, |
| 549 const std::string& grammar, | 566 const std::string& grammar, |
| 550 bool filter_profanities) { | 567 bool filter_profanities, |
| 568 bool show_notification) { |
| 551 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 569 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 552 VLOG(1) << "Requesting start (IO thread)"; | 570 VLOG(1) << "Requesting start (IO thread)"; |
| 553 | 571 |
| 554 // Everything put inside the lock to ensure the validity of context_getter, | 572 // Everything put inside the lock to ensure the validity of context_getter, |
| 555 // guaranteed while not in the shutdown state. Any ongoing or recognition | 573 // guaranteed while not in the shutdown state. Any ongoing or recognition |
| 556 // request will be requested to be aborted when entering the shutdown state. | 574 // request will be requested to be aborted when entering the shutdown state. |
| 557 base::AutoLock auto_lock(state_lock_); | 575 base::AutoLock auto_lock(state_lock_); |
| 558 if (state_ == kShutdown) | 576 if (state_ == kShutdown) |
| 559 return; | 577 return; |
| 560 | 578 |
| 579 // TODO(primiano) These two checks below could be avoided, since they are |
| 580 // already handled in the speech recognition classes. However, since the |
| 581 // speech input extensions tests are bypassing the manager, we need them to |
| 582 // pass the tests. A complete unit test which puts all the pieces together, |
| 583 // mocking just the endpoints (the audio input controller and the URL fetcher) |
| 584 // should be written. |
| 561 if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) { | 585 if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) { |
| 562 BrowserThread::PostTask( | 586 BrowserThread::PostTask( |
| 563 BrowserThread::UI, FROM_HERE, | 587 BrowserThread::UI, FROM_HERE, |
| 564 base::Bind(&SpeechInputExtensionManager::DispatchError, this, | 588 base::Bind(&SpeechInputExtensionManager::DispatchError, this, |
| 565 std::string(kErrorNoRecordingDeviceFound), false)); | 589 std::string(kErrorNoRecordingDeviceFound), false)); |
| 566 return; | 590 return; |
| 567 } | 591 } |
| 568 | 592 |
| 569 if (GetSpeechInputExtensionInterface()->IsCapturingAudio()) { | 593 if (GetSpeechInputExtensionInterface()->IsCapturingAudio()) { |
| 570 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 594 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 571 base::Bind(&SpeechInputExtensionManager::DispatchError, this, | 595 base::Bind(&SpeechInputExtensionManager::DispatchError, this, |
| 572 std::string(kErrorRecordingDeviceInUse), false)); | 596 std::string(kErrorRecordingDeviceInUse), false)); |
| 573 return; | 597 return; |
| 574 } | 598 } |
| 575 | 599 |
| 576 GetSpeechInputExtensionInterface()->StartRecording( | 600 GetSpeechInputExtensionInterface()->StartRecording(this, |
| 577 this, context_getter, kSpeechInputSessionId, language, grammar, | 601 context_getter, |
| 578 filter_profanities); | 602 extension_name, |
| 603 language, |
| 604 grammar, |
| 605 filter_profanities, |
| 606 show_notification); |
| 579 } | 607 } |
| 580 | 608 |
| 581 bool SpeechInputExtensionManager::HasAudioInputDevices() { | 609 bool SpeechInputExtensionManager::HasAudioInputDevices() { |
| 582 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 610 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 583 return SpeechRecognitionManager::GetInstance()->HasAudioInputDevices(); | 611 return SpeechRecognitionManager::GetInstance()->HasAudioInputDevices(); |
| 584 } | 612 } |
| 585 | 613 |
| 586 bool SpeechInputExtensionManager::IsCapturingAudio() { | 614 bool SpeechInputExtensionManager::IsCapturingAudio() { |
| 587 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 615 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 588 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); | 616 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); |
| 589 } | 617 } |
| 590 | 618 |
| 591 void SpeechInputExtensionManager::IsRecording( | 619 void SpeechInputExtensionManager::IsRecording( |
| 592 const IsRecordingCallback& callback) { | 620 const IsRecordingCallback& callback) { |
| 593 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 621 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 594 BrowserThread::PostTask( | 622 BrowserThread::PostTask( |
| 595 BrowserThread::IO, FROM_HERE, | 623 BrowserThread::IO, FROM_HERE, |
| 596 base::Bind(&SpeechInputExtensionManager::IsRecordingOnIOThread, | 624 base::Bind(&SpeechInputExtensionManager::IsRecordingOnIOThread, |
| 597 this, callback)); | 625 this, callback)); |
| 598 } | 626 } |
| 599 | 627 |
| 600 void SpeechInputExtensionManager::IsRecordingOnIOThread( | 628 void SpeechInputExtensionManager::IsRecordingOnIOThread( |
| 601 const IsRecordingCallback& callback) { | 629 const IsRecordingCallback& callback) { |
| 602 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 630 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 603 | 631 |
| 604 bool result = GetSpeechInputExtensionInterface()->IsCapturingAudio(); | 632 bool result = GetSpeechInputExtensionInterface()->IsCapturingAudio(); |
| 633 |
| 605 BrowserThread::PostTask( | 634 BrowserThread::PostTask( |
| 606 BrowserThread::UI, FROM_HERE, | 635 BrowserThread::UI, FROM_HERE, |
| 607 base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread, | 636 base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread, |
| 608 this, callback, result)); | 637 this, callback, result)); |
| 609 } | 638 } |
| 610 | 639 |
| 611 void SpeechInputExtensionManager::IsRecordingOnUIThread( | 640 void SpeechInputExtensionManager::IsRecordingOnUIThread( |
| 612 const IsRecordingCallback& callback, | 641 const IsRecordingCallback& callback, |
| 613 bool result) { | 642 bool result) { |
| 614 BrowserThread::CurrentlyOn(BrowserThread::UI); | 643 BrowserThread::CurrentlyOn(BrowserThread::UI); |
| 615 callback.Run(result); | 644 callback.Run(result); |
| 616 } | 645 } |
| 617 | 646 |
| 618 void SpeechInputExtensionManager::StartRecording( | 647 void SpeechInputExtensionManager::StartRecording( |
| 619 content::SpeechRecognitionEventListener* listener, | 648 content::SpeechRecognitionEventListener* listener, |
| 620 net::URLRequestContextGetter* context_getter, | 649 net::URLRequestContextGetter* context_getter, |
| 621 int session_id, | 650 const string16& extension_name, |
| 622 const std::string& language, | 651 const std::string& language, |
| 623 const std::string& grammar, | 652 const std::string& grammar, |
| 624 bool filter_profanities) { | 653 bool filter_profanities, |
| 654 bool show_notification) { |
| 625 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 655 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 626 DCHECK(!recognizer_); | 656 |
| 627 recognizer_ = content::SpeechRecognizer::Create( | 657 content::SpeechRecognitionSessionContext context; |
| 628 listener, session_id, language, grammar, context_getter, | 658 context.requested_by_page_element = false; |
| 629 filter_profanities, "", ""); | 659 context.is_first_request_for_context = show_notification; |
| 630 recognizer_->StartRecognition(); | 660 context.context_name = extension_name; |
| 661 |
| 662 content::SpeechRecognitionSessionConfig config; |
| 663 // config.is_one_shot = true; // TODO(primiano) Uncomment when CL2.0 lands. |
| 664 config.language = language; |
| 665 config.grammars.push_back(content::SpeechRecognitionGrammar(grammar)); |
| 666 config.initial_context = context; |
| 667 config.url_request_context_getter = context_getter; |
| 668 config.filter_profanities = filter_profanities; |
| 669 config.event_listener = listener; |
| 670 |
| 671 DCHECK(!is_recognition_in_progress_); |
| 672 SpeechRecognitionManager& manager = *SpeechRecognitionManager::GetInstance(); |
| 673 speech_recognition_session_id_ = |
| 674 manager.CreateSession(config); |
| 675 DCHECK_NE(speech_recognition_session_id_, |
| 676 SpeechRecognitionManager::kSessionIDInvalid); |
| 677 is_recognition_in_progress_ = true; |
| 678 manager.StartSession(speech_recognition_session_id_); |
| 631 } | 679 } |
| 632 | 680 |
| 633 bool SpeechInputExtensionManager::HasValidRecognizer() { | 681 bool SpeechInputExtensionManager::HasValidRecognizer() { |
| 634 return !!recognizer_; | 682 if (!is_recognition_in_progress_) |
| 683 return false; |
| 684 return SpeechRecognitionManager::GetInstance()->IsCapturingAudio(); |
| 635 } | 685 } |
| 636 | 686 |
| 637 bool SpeechInputExtensionManager::Stop(const std::string& extension_id, | 687 bool SpeechInputExtensionManager::Stop(const std::string& extension_id, |
| 638 std::string* error) { | 688 std::string* error) { |
| 639 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 689 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 640 DCHECK(error); | 690 DCHECK(error); |
| 641 VLOG(1) << "Requesting stop (UI thread)"; | 691 VLOG(1) << "Requesting stop (UI thread)"; |
| 642 | 692 |
| 643 base::AutoLock auto_lock(state_lock_); | 693 base::AutoLock auto_lock(state_lock_); |
| 644 if (state_ == kShutdown || | 694 if (state_ == kShutdown || |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 685 GetSpeechInputExtensionInterface()->StopRecording(false); | 735 GetSpeechInputExtensionInterface()->StopRecording(false); |
| 686 | 736 |
| 687 if (state_ == kShutdown) | 737 if (state_ == kShutdown) |
| 688 return; | 738 return; |
| 689 | 739 |
| 690 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 740 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 691 base::Bind(&SpeechInputExtensionManager::StopSucceededOnUIThread, this)); | 741 base::Bind(&SpeechInputExtensionManager::StopSucceededOnUIThread, this)); |
| 692 } | 742 } |
| 693 | 743 |
| 694 void SpeechInputExtensionManager::StopRecording(bool recognition_failed) { | 744 void SpeechInputExtensionManager::StopRecording(bool recognition_failed) { |
| 695 if (recognizer_) { | 745 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 696 // Recognition is already cancelled in case of failure. | 746 if (!is_recognition_in_progress_) |
| 697 // Double-cancelling leads to assertion failures. | 747 return; |
| 698 if (!recognition_failed) | 748 DCHECK_NE(speech_recognition_session_id_, |
| 699 recognizer_->AbortRecognition(); | 749 SpeechRecognitionManager::kSessionIDInvalid); |
| 700 recognizer_.release(); | 750 SpeechRecognitionManager::GetInstance()->AbortSession( |
| 701 } | 751 speech_recognition_session_id_); |
| 702 } | 752 } |
| 703 | 753 |
| 704 void SpeechInputExtensionManager::StopSucceededOnUIThread() { | 754 void SpeechInputExtensionManager::StopSucceededOnUIThread() { |
| 705 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 755 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 706 VLOG(1) << "Stop succeeded (UI thread)"; | 756 VLOG(1) << "Stop succeeded (UI thread)"; |
| 707 | 757 |
| 708 base::AutoLock auto_lock(state_lock_); | 758 base::AutoLock auto_lock(state_lock_); |
| 709 if (state_ == kShutdown) | 759 if (state_ == kShutdown) |
| 710 return; | 760 return; |
| 711 | 761 |
| 712 std::string extension_id = extension_id_in_use_; | 762 std::string extension_id = extension_id_in_use_; |
| 713 ResetToIdleState(); | 763 ResetToIdleState(); |
| 714 | 764 |
| 715 DCHECK(notification_.get()); | |
| 716 notification_->Hide(); | |
| 717 | |
| 718 content::NotificationService::current()->Notify( | 765 content::NotificationService::current()->Notify( |
| 719 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED, | 766 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED, |
| 720 // Guarded by the state_ == kShutdown check. | 767 // Guarded by the state_ == kShutdown check. |
| 721 content::Source<Profile>(profile_), | 768 content::Source<Profile>(profile_), |
| 722 content::Details<std::string>(&extension_id)); | 769 content::Details<std::string>(&extension_id)); |
| 723 } | 770 } |
| 724 | 771 |
| 725 void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id, | 772 void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id, |
| 726 float volume, | 773 float volume, |
| 727 float noise_volume) { | 774 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 |