Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(436)

Side by Side Diff: chrome/browser/extensions/speech_input/extension_speech_input_manager.cc

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

Powered by Google App Engine
This is Rietveld 408576698