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