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

Side by Side Diff: chrome/browser/extensions/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: 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
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 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 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/extensions/extension_speech_input_manager.h" 5 #include "chrome/browser/extensions/extension_speech_input_manager.h"
6 6
7 #include "base/json/json_writer.h"
8 #include "base/utf_string_conversions.h"
9 #include "base/values.h"
10 #include "chrome/browser/extensions/extension_event_router.h"
11 #include "chrome/browser/extensions/extension_speech_input_api_constants.h"
12 #include "chrome/browser/profiles/profile.h"
7 #include "chrome/browser/profiles/profile_dependency_manager.h" 13 #include "chrome/browser/profiles/profile_dependency_manager.h"
8 #include "chrome/browser/profiles/profile_keyed_service.h" 14 #include "chrome/browser/profiles/profile_keyed_service.h"
9 #include "chrome/browser/profiles/profile_keyed_service_factory.h" 15 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
10 #include "chrome/common/chrome_notification_types.h" 16 #include "chrome/common/chrome_notification_types.h"
11 #include "chrome/common/extensions/extension.h" 17 #include "chrome/common/extensions/extension.h"
18 #include "content/browser/browser_thread.h"
12 #include "content/common/notification_service.h" 19 #include "content/common/notification_service.h"
13 20
21 using namespace speech_input;
22 namespace constants = extension_speech_input_api_constants;
23
14 namespace { 24 namespace {
15 25
16 // Wrap an ExtensionSpeechInputManager using scoped_refptr to avoid 26 // Wrap an ExtensionSpeechInputManager using scoped_refptr to avoid
17 // assertion failures on destruction because of not using release(). 27 // assertion failures on destruction because of not using release().
18 class ExtensionSpeechInputManagerWrapper : public ProfileKeyedService { 28 class ExtensionSpeechInputManagerWrapper : public ProfileKeyedService {
19 public: 29 public:
20 explicit ExtensionSpeechInputManagerWrapper( 30 explicit ExtensionSpeechInputManagerWrapper(
21 ExtensionSpeechInputManager* manager) 31 ExtensionSpeechInputManager* manager)
22 : manager_(manager) {} 32 : manager_(manager) {}
23 33
(...skipping 26 matching lines...) Expand all
50 Factory(); 60 Factory();
51 virtual ~Factory(); 61 virtual ~Factory();
52 62
53 // ProfileKeyedServiceFactory methods: 63 // ProfileKeyedServiceFactory methods:
54 virtual ProfileKeyedService* BuildServiceInstanceFor( 64 virtual ProfileKeyedService* BuildServiceInstanceFor(
55 Profile* profile) const; 65 Profile* profile) const;
56 virtual bool ServiceRedirectedInIncognito() { return false; } 66 virtual bool ServiceRedirectedInIncognito() { return false; }
57 virtual bool ServiceIsNULLWhileTesting() { return true; } 67 virtual bool ServiceIsNULLWhileTesting() { return true; }
58 virtual bool ServiceIsCreatedWithProfile() { return true; } 68 virtual bool ServiceIsCreatedWithProfile() { return true; }
59 69
70 static int per_profile_id_;
Satish 2011/10/08 23:15:07 add a comment here explaining that this is used fo
Leandro Graciá Gil 2011/10/09 10:29:21 Done.
71
60 DISALLOW_COPY_AND_ASSIGN(Factory); 72 DISALLOW_COPY_AND_ASSIGN(Factory);
61 }; 73 };
62 74
75 int ExtensionSpeechInputManager::Factory::per_profile_id_ = 0;
76
63 void ExtensionSpeechInputManager::Factory::Initialize() { 77 void ExtensionSpeechInputManager::Factory::Initialize() {
64 GetInstance(); 78 GetInstance();
65 } 79 }
66 80
67 ExtensionSpeechInputManager::Factory* 81 ExtensionSpeechInputManager::Factory*
68 ExtensionSpeechInputManager::Factory::GetInstance() { 82 ExtensionSpeechInputManager::Factory::GetInstance() {
69 return Singleton<ExtensionSpeechInputManager::Factory>::get(); 83 return Singleton<ExtensionSpeechInputManager::Factory>::get();
70 } 84 }
71 85
72 ExtensionSpeechInputManagerWrapper* 86 ExtensionSpeechInputManagerWrapper*
73 ExtensionSpeechInputManager::Factory::GetForProfile( 87 ExtensionSpeechInputManager::Factory::GetForProfile(
74 Profile* profile) { 88 Profile* profile) {
75 return static_cast<ExtensionSpeechInputManagerWrapper*>( 89 return static_cast<ExtensionSpeechInputManagerWrapper*>(
76 GetServiceForProfile(profile, true)); 90 GetServiceForProfile(profile, true));
77 } 91 }
78 92
79 ExtensionSpeechInputManager::Factory::Factory() 93 ExtensionSpeechInputManager::Factory::Factory()
80 : ProfileKeyedServiceFactory(ProfileDependencyManager::GetInstance()) { 94 : ProfileKeyedServiceFactory(ProfileDependencyManager::GetInstance()) {
81 } 95 }
82 96
83 ExtensionSpeechInputManager::Factory::~Factory() { 97 ExtensionSpeechInputManager::Factory::~Factory() {
84 } 98 }
85 99
86 ProfileKeyedService* 100 ProfileKeyedService*
87 ExtensionSpeechInputManager::Factory::BuildServiceInstanceFor( 101 ExtensionSpeechInputManager::Factory::BuildServiceInstanceFor(
88 Profile* profile) const { 102 Profile* profile) const {
89 scoped_refptr<ExtensionSpeechInputManager> manager( 103 scoped_refptr<ExtensionSpeechInputManager> manager(
90 new ExtensionSpeechInputManager(profile)); 104 new ExtensionSpeechInputManager(profile, ++per_profile_id_));
91 return new ExtensionSpeechInputManagerWrapper(manager); 105 return new ExtensionSpeechInputManagerWrapper(manager);
92 } 106 }
93 107
94 ExtensionSpeechInputManager::ExtensionSpeechInputManager(Profile* profile) 108 ExtensionSpeechInputManager::ExtensionSpeechInputManager(Profile* profile,
109 size_t speech_caller_id)
95 : profile_(profile), 110 : profile_(profile),
96 extension_(NULL) { 111 speech_caller_id_(speech_caller_id),
112 state_(kIdle) {
97 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 113 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
98 Source<Profile>(profile_)); 114 Source<Profile>(profile_));
99 } 115 }
100 116
101 ExtensionSpeechInputManager::~ExtensionSpeechInputManager() { 117 ExtensionSpeechInputManager::~ExtensionSpeechInputManager() {
102 } 118 }
103 119
104 ExtensionSpeechInputManager* ExtensionSpeechInputManager::GetForProfile( 120 ExtensionSpeechInputManager* ExtensionSpeechInputManager::GetForProfile(
105 Profile* profile) { 121 Profile* profile) {
106 ExtensionSpeechInputManagerWrapper *wrapper = 122 ExtensionSpeechInputManagerWrapper *wrapper =
(...skipping 10 matching lines...) Expand all
117 void ExtensionSpeechInputManager::Observe(int type, 133 void ExtensionSpeechInputManager::Observe(int type,
118 const NotificationSource& source, 134 const NotificationSource& source,
119 const NotificationDetails& details) { 135 const NotificationDetails& details) {
120 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) 136 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED)
121 ExtensionUnloaded(Details<UnloadedExtensionInfo>(details)->extension->id()); 137 ExtensionUnloaded(Details<UnloadedExtensionInfo>(details)->extension->id());
122 else 138 else
123 NOTREACHED(); 139 NOTREACHED();
124 } 140 }
125 141
126 void ExtensionSpeechInputManager::ShutdownOnUIThread() { 142 void ExtensionSpeechInputManager::ShutdownOnUIThread() {
127 // TODO(leandrogracia): Force stop to speech recognition if active. 143 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
144 VLOG(1) << "Profile shutting down.";
145
146 state_lock_.Acquire();
147 DCHECK(state_ != kShutdown);
148 if (state_ != kIdle) {
149 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
150 NewRunnableMethod(this,
151 &ExtensionSpeechInputManager::ForceStopOnIOThread));
152 }
153 state_ = kShutdown;
154 VLOG(1) << "Entering the shutdown sink state.";
128 registrar_.RemoveAll(); 155 registrar_.RemoveAll();
129 profile_ = NULL; 156 profile_ = NULL;
157 state_lock_.Release();
158 }
159
160 bool ExtensionSpeechInputManager::ShuttingDown() {
161 state_lock_.Acquire();
162 bool shutting_down = state_ == kShutdown;
163 state_lock_.Release();
164 return shutting_down;
130 } 165 }
131 166
132 void ExtensionSpeechInputManager::ExtensionUnloaded(const std::string& id) { 167 void ExtensionSpeechInputManager::ExtensionUnloaded(const std::string& id) {
133 // TODO(leandrogracia): Force stop to speech recognition if the extension 168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
134 // is currently using the API. 169
135 } 170 VLOG(1) << "Extension unloaded. Requesting to enforce stop...";
136 171 state_lock_.Acquire();
137 void ExtensionSpeechInputManager::Start(const Extension *extension) { 172 if (state_ == kShutdown) {
138 // TODO(leandrogracia): Start speech recognition. 173 state_lock_.Release();
139 } 174 return;
140 175 }
141 void ExtensionSpeechInputManager::Stop(const Extension *extension) { 176
142 // TODO(leandrogracia): Stop speech recognition. 177 if (!extension_id_.empty() && extension_id_ == id) {
143 } 178 extension_id_.clear();
144 179
180 if (state_ != kIdle) {
181 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
182 NewRunnableMethod(this,
183 &ExtensionSpeechInputManager::ForceStopOnIOThread));
184 }
185 }
186 state_lock_.Release();
187 }
188
189 void ExtensionSpeechInputManager::SetRecognitionResult(
190 int caller_id,
191 const SpeechInputResult& result) {
192 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
193 DCHECK_EQ(caller_id, speech_caller_id_);
194
195 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
196 NewRunnableMethod(this,
197 &ExtensionSpeechInputManager::SetRecognitionResultOnUIThread, result));
198 }
199
200 void ExtensionSpeechInputManager::SetRecognitionResultOnUIThread(
201 const SpeechInputResult& result) {
202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203
204 state_lock_.Acquire();
205 if (state_ == kShutdown) {
206 state_lock_.Release();
207 return;
208 }
209 state_ = kIdle;
210 state_lock_.Release();
211
212 ListValue args;
213 DictionaryValue* js_event = new DictionaryValue();
214 args.Append(js_event);
215
216 ListValue* js_hypothesis_array = new ListValue();
217 js_event->Set(constants::kHypothesesKey, js_hypothesis_array);
218
219 for (size_t i = 0; i < result.hypotheses.size(); ++i) {
220 const SpeechInputHypothesis& hypothesis = result.hypotheses[i];
221
222 DictionaryValue* js_hypothesis_object = new DictionaryValue();
223 js_hypothesis_array->Append(js_hypothesis_object);
224
225 js_hypothesis_object->SetString(constants::kUtteranceKey,
226 UTF16ToUTF8(hypothesis.utterance));
227 js_hypothesis_object->SetDouble(constants::kConfidenceKey,
228 hypothesis.confidence);
229 }
230
231 std::string json_args;
232 base::JSONWriter::Write(&args, false, &json_args);
233 VLOG(1) << "Results: " << json_args;
234 DispatchEventToExtension(constants::kOnSpeechResultEvent, json_args);
235 }
236
237 void ExtensionSpeechInputManager::DidStartReceivingAudio(int caller_id) {
238 VLOG(1) << "DidStartReceivingAudio";
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
240 DCHECK_EQ(caller_id, speech_caller_id_);
241
242 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
243 NewRunnableMethod(this,
244 &ExtensionSpeechInputManager::DidStartReceivingAudioOnUIThread));
245 }
246
247 void ExtensionSpeechInputManager::DidCompleteRecording(int caller_id) {
248 VLOG(1) << "DidCompleteRecording";
249 DCHECK_EQ(caller_id, speech_caller_id_);
250 }
251
252 void ExtensionSpeechInputManager::DidCompleteRecognition(int caller_id) {
253 VLOG(1) << "DidCompleteRecognition";
254 DCHECK_EQ(caller_id, speech_caller_id_);
255 }
256
257 void ExtensionSpeechInputManager::DidStartReceivingAudioOnUIThread() {
258 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
259
260 state_lock_.Acquire();
261 if (state_ == kShutdown) {
262 state_lock_.Release();
263 return;
264 }
265
266 DCHECK_EQ(state_, kStarting);
267 VLOG(1) << "State changed to recording";
268 state_ = kRecording;
269 state_lock_.Release();
270
271 VLOG(1) << "Sending start notification";
272 NotificationService::current()->Notify(
273 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_STARTED,
274 Source<Profile>(profile_),
275 Details<std::string>(&extension_id_));
276 }
277
278 void ExtensionSpeechInputManager::OnRecognizerError(
279 int caller_id, SpeechInputError error) {
280 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
281 DCHECK_EQ(caller_id, speech_caller_id_);
282 VLOG(1) << "OnRecognizerError: " << error;
283
284 state_lock_.Acquire();
285 if (state_ == kShutdown) {
286 state_lock_.Release();
287 return;
288 }
289
290 std::string event_error_code;
291 switch (error) {
292 case kErrorNone:
293 break;
294
295 case kErrorAudio:
296 if (state_ == kStarting)
297 event_error_code = constants::kErrorUnableToStart;
298 else
299 event_error_code = constants::kErrorCaptureError;
300 break;
301
302 case kErrorNetwork:
303 event_error_code = constants::kErrorNetworkError;
304 break;
305
306 case kErrorBadGrammar:
307 // No error is returned on invalid language, for example.
308 // To avoid confusion about when this is would be fired, the invalid
309 // params error is not being exposed to the onError event.
310 event_error_code = constants::kErrorUnableToStart;
311 break;
312
313 case kErrorNoSpeech:
314 event_error_code = constants::kErrorNoSpeechHeard;
315 break;
316
317 case kErrorNoMatch:
318 event_error_code = constants::kErrorNoResults;
319 break;
320
321 // The remaining kErrorAborted case should never be returned by the server.
322 default:
323 NOTREACHED();
324 }
325
326 VLOG(1) << "State changed to idle";
327 state_ = kIdle;
328 state_lock_.Release();
329
330 if (!event_error_code.empty())
331 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
332 NewRunnableMethod(this,
333 &ExtensionSpeechInputManager::DispatchError, event_error_code));
334 }
335
336 void ExtensionSpeechInputManager::DidCompleteEnvironmentEstimation(
337 int caller_id) {
338 VLOG(1) << "DidCompleteEnvironmentEstimation";
339 DCHECK_EQ(caller_id, speech_caller_id_);
340 }
341
342 void ExtensionSpeechInputManager::DidStartReceivingSpeech(int caller_id) {
343 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
344 DCHECK_EQ(caller_id, speech_caller_id_);
345 VLOG(1) << "DidStartReceivingSpeech";
346
347 ListValue args;
348 std::string json_args;
349 base::JSONWriter::Write(&args, false, &json_args);
350
351 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
352 NewRunnableMethod(this,
353 &ExtensionSpeechInputManager::DispatchEventToExtension,
354 std::string(constants::kOnSoundStartEvent), json_args));
355 }
356
357 void ExtensionSpeechInputManager::DidStopReceivingSpeech(int caller_id) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
359 DCHECK_EQ(caller_id, speech_caller_id_);
360 VLOG(1) << "DidStopReceivingSpeech";
361
362 ListValue args;
363 std::string json_args;
364 base::JSONWriter::Write(&args, false, &json_args);
365
366 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
367 NewRunnableMethod(this,
368 &ExtensionSpeechInputManager::DispatchEventToExtension,
369 std::string(constants::kOnSoundEndEvent), json_args));
370 }
371
372 void ExtensionSpeechInputManager::DispatchEventToExtension(
373 const std::string& event, const std::string& json_args) {
374 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
375
376 if (ShuttingDown())
377 return;
378
379 if (profile_ && profile_->GetExtensionEventRouter())
380 profile_->GetExtensionEventRouter()->DispatchEventToExtension(
381 extension_id_, event, json_args, profile_, GURL());
382 }
383
384 void ExtensionSpeechInputManager::DispatchError(const std::string& code) {
385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
386
387 if (ShuttingDown())
388 return;
389
390 NotificationService::current()->Notify(
391 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED,
392 Source<Profile>(profile_),
393 Details<std::string>(&extension_id_));
394
395 ListValue args;
396 DictionaryValue *js_error = new DictionaryValue();
397 args.Append(js_error);
398 js_error->SetString(constants::kErrorCodeKey, code);
399 std::string json_args;
400 base::JSONWriter::Write(&args, false, &json_args);
401 DispatchEventToExtension(constants::kOnErrorEvent, json_args);
402 }
403
404 void ExtensionSpeechInputManager::Start(const std::string& extension_id,
405 const std::string& language, const std::string& grammar,
406 bool filter_profanities) {
407 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
408 VLOG(1) << "Requesting start (UI thread)";
409
410 if (ShuttingDown())
411 return;
412
413 if (!extension_id_.empty() && extension_id != extension_id_) {
414 DispatchError(constants::kErrorRequestDenied);
415 return;
416 }
417
418 state_lock_.Acquire();
419 if (state_ != kIdle) {
420 DCHECK(recognizer_);
421 state_lock_.Release();
422 return;
423 }
424
425 extension_id_ = extension_id;
426 VLOG(1) << "Start";
427
428 VLOG(1) << "State changed to starting";
429 state_ = kStarting;
430 state_lock_.Release();
431
432 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
433 NewRunnableMethod(this, &ExtensionSpeechInputManager::StartOnIOThread,
434 profile_->GetRequestContext(), language, grammar, filter_profanities));
435 }
436
437 void ExtensionSpeechInputManager::StartOnIOThread(
438 net::URLRequestContextGetter* context_getter,
439 const std::string& language, const std::string& grammar,
440 bool filter_profanities) {
441 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
442 VLOG(1) << "Requesting start (IO thread)";
443
444 // Everything put inside the lock to ensure the validity of context_getter,
445 // guaranteed while not in the shutdown state. Any ongoing or recognition
446 // request will be requested to be aborted when entering the shutdown state.
447 state_lock_.Acquire();
448 if (state_ == kShutdown) {
449 state_lock_.Release();
450 return;
451 }
452
453 if (!AudioManager::GetAudioManager()->HasAudioInputDevices()) {
454 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
455 NewRunnableMethod(this, &ExtensionSpeechInputManager::DispatchError,
456 std::string(constants::kErrorNoDevicesAvailable)));
457 state_lock_.Release();
458 return;
459 }
460
461 recognizer_ = new SpeechRecognizer(this, speech_caller_id_, language, grammar,
Satish 2011/10/08 23:15:07 since you are directly using a SpeechRecognizer ob
Leandro Graciá Gil 2011/10/09 10:29:21 If that happens they will clash but also produce a
Satish 2011/10/09 20:01:47 There is one issue - if an extension was already d
462 context_getter, filter_profanities, "", "");
463 recognizer_->StartRecording();
464 state_lock_.Release();
465 }
466
467 void ExtensionSpeechInputManager::Stop(const std::string& extension_id) {
468 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
469 VLOG(1) << "Requesting stop (UI thread)";
470
471 if (ShuttingDown())
472 return;
473
474 if (!extension_id_.empty() && extension_id != extension_id_) {
475 DispatchError(constants::kErrorRequestDenied);
476 return;
477 }
478
479 state_lock_.Acquire();
480 if (state_ != kRecording) {
481 state_lock_.Release();
482 return;
483 }
484
485 DCHECK(recognizer_);
486
487 VLOG(1) << "State changed to stopping";
488 state_ = kStopping;
489 state_lock_.Release();
490
491 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
492 NewRunnableMethod(this,
493 &ExtensionSpeechInputManager::ForceStopOnIOThread));
494 }
495
496 void ExtensionSpeechInputManager::ForceStopOnIOThread() {
497 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
498 VLOG(1) << "Requesting forced stop (IO thread)";
499
500 state_lock_.Acquire();
501 DCHECK(state_ != kIdle);
502
503 if (recognizer_) {
504 recognizer_->CancelRecognition();
505 recognizer_ = NULL;
506 }
507
508 if (state_ == kShutdown) {
509 state_lock_.Release();
510 return;
511 }
512
513 state_ = kIdle;
514 VLOG(1) << "State changed to idle";
515
516 NotificationService::current()->Notify(
517 chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_STOPPED,
518 Source<Profile>(profile_), // Guarded by the state_ == kShutdown check.
519 Details<std::string>(&extension_id_));
520
521 extension_id_.clear();
522 state_lock_.Release();
523 }
524
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698