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

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 media code and dependencies. Addressed in a different patch. 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 /*{
Leandro GraciĆ” Gil 2011/10/12 18:24:46 Oops, this should be removed as it's not required.
219 base::AutoLock auto_lock(state_lock_);
220 if (state_ == kShutdown)
221 return;
222 state_ = kIdle;
223 }*/
224
225 ListValue args;
226 DictionaryValue* js_event = new DictionaryValue();
227 args.Append(js_event);
228
229 ListValue* js_hypothesis_array = new ListValue();
230 js_event->Set(constants::kHypothesesKey, js_hypothesis_array);
231
232 for (size_t i = 0; i < result.hypotheses.size(); ++i) {
233 const SpeechInputHypothesis& hypothesis = result.hypotheses[i];
234
235 DictionaryValue* js_hypothesis_object = new DictionaryValue();
236 js_hypothesis_array->Append(js_hypothesis_object);
237
238 js_hypothesis_object->SetString(constants::kUtteranceKey,
239 UTF16ToUTF8(hypothesis.utterance));
240 js_hypothesis_object->SetDouble(constants::kConfidenceKey,
241 hypothesis.confidence);
242 }
243
244 std::string json_args;
245 base::JSONWriter::Write(&args, false, &json_args);
246 VLOG(1) << "Results: " << json_args;
247 DispatchEventToExtension(extension_id, constants::kOnResultEvent, json_args);
248 }
249
250 void ExtensionSpeechInputManager::DidStartReceivingAudio(int caller_id) {
251 VLOG(1) << "DidStartReceivingAudio";
252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
253 DCHECK_EQ(caller_id, kSpeechCallerId);
254
255 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
256 base::Bind(&ExtensionSpeechInputManager::DidStartReceivingAudioOnUIThread,
257 this));
258 }
259
260 void ExtensionSpeechInputManager::DidCompleteRecording(int caller_id) {
261 DCHECK_EQ(caller_id, kSpeechCallerId);
262 }
263
264 void ExtensionSpeechInputManager::DidCompleteRecognition(int caller_id) {
265 DCHECK_EQ(caller_id, kSpeechCallerId);
266 }
267
268 void ExtensionSpeechInputManager::DidStartReceivingAudioOnUIThread() {
269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
270
271 base::AutoLock auto_lock(state_lock_);
272 if (state_ == kShutdown)
273 return;
274
275 DCHECK_EQ(state_, kStarting);
276 VLOG(1) << "State changed to recording";
277 state_ = kRecording;
278
279 VLOG(1) << "Sending start notification";
280 NotificationService::current()->Notify(
281 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED,
282 Source<Profile>(profile_),
283 Details<std::string>(&extension_id_in_use_));
284 }
285
286 void ExtensionSpeechInputManager::OnRecognizerError(
287 int caller_id, SpeechInputError error) {
288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
289 DCHECK_EQ(caller_id, kSpeechCallerId);
290 VLOG(1) << "OnRecognizerError: " << error;
291
292 base::AutoLock auto_lock(state_lock_);
293 if (state_ == kShutdown)
294 return;
295
296 // Release the recognizer object.
297 GetExtensionSpeechInterface()->StopRecording(true);
298
299 std::string event_error_code;
300 bool report_to_event = true;
301
302 switch (error) {
303 case kErrorNone:
304 break;
305
306 case kErrorAudio:
307 if (state_ == kStarting) {
308 event_error_code = constants::kErrorUnableToStart;
309 report_to_event = false;
310 } else {
311 event_error_code = constants::kErrorCaptureError;
312 }
313 break;
314
315 case kErrorNetwork:
316 event_error_code = constants::kErrorNetworkError;
317 break;
318
319 case kErrorBadGrammar:
320 // No error is returned on invalid language, for example.
321 // To avoid confusion about when this is would be fired, the invalid
322 // params error is not being exposed to the onError event.
323 event_error_code = constants::kErrorUnableToStart;
324 break;
325
326 case kErrorNoSpeech:
327 event_error_code = constants::kErrorNoSpeechHeard;
328 break;
329
330 case kErrorNoMatch:
331 event_error_code = constants::kErrorNoResults;
332 break;
333
334 // The remaining kErrorAborted case should never be returned by the server.
335 default:
336 NOTREACHED();
337 }
338
339 if (!event_error_code.empty()) {
340 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
341 base::Bind(&ExtensionSpeechInputManager::DispatchError,
342 this, event_error_code, report_to_event));
343 }
344 }
345
346 void ExtensionSpeechInputManager::DidCompleteEnvironmentEstimation(
347 int caller_id) {
348 DCHECK_EQ(caller_id, kSpeechCallerId);
349 }
350
351 void ExtensionSpeechInputManager::DidStartReceivingSpeech(int caller_id) {
352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
353 DCHECK_EQ(caller_id, kSpeechCallerId);
354 VLOG(1) << "DidStartReceivingSpeech";
355
356 std::string json_args;
357 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
358 base::Bind(&ExtensionSpeechInputManager::DispatchEventToExtension,
359 this, extension_id_in_use_, std::string(constants::kOnSoundStartEvent),
360 json_args));
361 }
362
363 void ExtensionSpeechInputManager::DidStopReceivingSpeech(int caller_id) {
364 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
365 DCHECK_EQ(caller_id, kSpeechCallerId);
366 VLOG(1) << "DidStopReceivingSpeech";
367
368 std::string json_args;
369 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
370 base::Bind(&ExtensionSpeechInputManager::DispatchEventToExtension,
371 this, extension_id_in_use_, std::string(constants::kOnSoundEndEvent),
372 json_args));
373 }
374
375 void ExtensionSpeechInputManager::DispatchEventToExtension(
376 const std::string& extension_id, const std::string& event,
377 const std::string& json_args) {
378 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
379
380 base::AutoLock auto_lock(state_lock_);
381 if (state_ == kShutdown)
382 return;
383
384 if (profile_ && profile_->GetExtensionEventRouter()) {
385 std::string final_args;
386 if (json_args.empty()) {
387 ListValue args;
388 base::JSONWriter::Write(&args, false, &final_args);
389 } else {
390 final_args = json_args;
391 }
392
393 profile_->GetExtensionEventRouter()->DispatchEventToExtension(
394 extension_id, event, final_args, profile_, GURL());
395 }
396 }
397
398 void ExtensionSpeechInputManager::DispatchError(
399 const std::string& error, bool dispatch_event) {
400 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
401
402 std::string extension_id;
403 {
404 base::AutoLock auto_lock(state_lock_);
405 if (state_ == kShutdown)
406 return;
407
408 extension_id = extension_id_in_use_;
409 ResetToIdleState();
410
411 // Will set the error property in the ongoing extension function calls.
412 ExtensionError details(extension_id, error);
413 NotificationService::current()->Notify(
414 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED,
415 Source<Profile>(profile_),
416 Details<ExtensionError>(&details));
417 }
418
419 // Used for errors that are also reported via the onError event.
420 if (dispatch_event) {
421 ListValue args;
422 DictionaryValue *js_error = new DictionaryValue();
423 args.Append(js_error);
424 js_error->SetString(constants::kErrorCodeKey, error);
425 std::string json_args;
426 base::JSONWriter::Write(&args, false, &json_args);
427 DispatchEventToExtension(extension_id,
428 constants::kOnErrorEvent, json_args);
429 }
430 }
431
432 bool ExtensionSpeechInputManager::Start(const std::string& extension_id,
433 const std::string& language, const std::string& grammar,
434 bool filter_profanities, std::string* error) {
435 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
436 DCHECK(error);
437 VLOG(1) << "Requesting start (UI thread)";
438
439 base::AutoLock auto_lock(state_lock_);
440 if (state_ == kShutdown ||
441 (!extension_id_in_use_.empty() && extension_id_in_use_ != extension_id)) {
442 *error = constants::kErrorRequestDenied;
443 return false;
444 }
445
446 switch (state_) {
447 case kIdle:
448 break;
449
450 case kStarting:
451 *error = constants::kErrorRequestInProgress;
452 return false;
453
454 case kRecording:
455 case kStopping:
456 *error = constants::kErrorInvalidOperation;
457 return false;
458
459 default:
460 NOTREACHED();
461 }
462
463 extension_id_in_use_ = extension_id;
464 VLOG(1) << "State changed to starting";
465 state_ = kStarting;
466
467 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
468 base::Bind(&ExtensionSpeechInputManager::StartOnIOThread, this,
469 profile_->GetRequestContext(), language, grammar, filter_profanities));
470 return true;
471 }
472
473 void ExtensionSpeechInputManager::StartOnIOThread(
474 net::URLRequestContextGetter* context_getter,
475 const std::string& language, const std::string& grammar,
476 bool filter_profanities) {
477 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
478 VLOG(1) << "Requesting start (IO thread)";
479
480 // Everything put inside the lock to ensure the validity of context_getter,
481 // guaranteed while not in the shutdown state. Any ongoing or recognition
482 // request will be requested to be aborted when entering the shutdown state.
483 base::AutoLock auto_lock(state_lock_);
484 if (state_ == kShutdown)
485 return;
486
487 if (!GetExtensionSpeechInterface()->CheckForAvailableDevices()) {
488 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
489 base::Bind(&ExtensionSpeechInputManager::DispatchError, this,
490 std::string(constants::kErrorNoDevicesAvailable), false));
491 return;
492 }
493
494 GetExtensionSpeechInterface()->StartRecording(this, context_getter,
495 kSpeechCallerId, language, grammar, filter_profanities);
496 }
497
498 bool ExtensionSpeechInputManager::CheckForAvailableDevices() {
499 // TODO(leandrogracia): ensure recording exclusivity with other features
500 // in Chrome like web speech input.
501 return AudioManager::GetAudioManager()->HasAudioInputDevices();
502 }
503
504 void ExtensionSpeechInputManager::StartRecording(
505 speech_input::SpeechRecognizerDelegate* delegate,
506 net::URLRequestContextGetter* context_getter, int caller_id,
507 const std::string& language, const std::string& grammar,
508 bool filter_profanities) {
509 DCHECK(!recognizer_);
510 recognizer_ = new SpeechRecognizer(delegate, caller_id, language, grammar,
511 context_getter, filter_profanities, "", "");
512 recognizer_->StartRecording();
513 }
514
515 bool ExtensionSpeechInputManager::HasValidRecognizer() const {
516 // Conditional expression used to avoid a performance warning on windows.
517 return recognizer_ ? true : false;
518 }
519
520 bool ExtensionSpeechInputManager::Stop(const std::string& extension_id,
521 std::string* error) {
522 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
523 DCHECK(error);
524 VLOG(1) << "Requesting stop (UI thread)";
525
526 base::AutoLock auto_lock(state_lock_);
527 if (state_ == kShutdown ||
528 (!extension_id_in_use_.empty() && extension_id_in_use_ != extension_id)) {
529 *error = constants::kErrorRequestDenied;
530 return false;
531 }
532
533 switch (state_) {
534 case kRecording:
535 break;
536
537 case kStopping:
538 *error = constants::kErrorRequestInProgress;
539 return false;
540
541 case kIdle:
542 case kStarting:
543 *error = constants::kErrorInvalidOperation;
544 return false;
545
546 default:
547 NOTREACHED();
548 }
549
550 // Guarded by the state lock.
551 DCHECK(GetExtensionSpeechInterface()->HasValidRecognizer());
552
553 VLOG(1) << "State changed to stopping";
554 state_ = kStopping;
555
556 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
557 base::Bind(&ExtensionSpeechInputManager::ForceStopOnIOThread, this));
558 return true;
559 }
560
561 void ExtensionSpeechInputManager::ForceStopOnIOThread() {
562 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
563 VLOG(1) << "Requesting forced stop (IO thread)";
564
565 base::AutoLock auto_lock(state_lock_);
566 DCHECK(state_ != kIdle);
567
568 GetExtensionSpeechInterface()->StopRecording(false);
569
570 if (state_ == kShutdown)
571 return;
572
573 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
574 base::Bind(&ExtensionSpeechInputManager::StopSucceededOnUIThread, this));
575 }
576
577 void ExtensionSpeechInputManager::StopRecording(bool recognition_failed) {
578 if (recognizer_) {
579 // Recognition is already cancelled in case of failure.
580 // Double-cancelling leads to assertion failures.
581 if (!recognition_failed)
582 recognizer_->CancelRecognition();
583 recognizer_.release();
584 }
585 }
586
587 void ExtensionSpeechInputManager::StopSucceededOnUIThread() {
588 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
589 VLOG(1) << "Stop succeeded (UI thread)";
590
591 base::AutoLock auto_lock(state_lock_);
592 if (state_ == kShutdown)
593 return;
594
595 std::string extension_id = extension_id_in_use_;
596 ResetToIdleState();
597
598 NotificationService::current()->Notify(
599 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED,
600 Source<Profile>(profile_), // Guarded by the state_ == kShutdown check.
601 Details<std::string>(&extension_id));
602 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698