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

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

Powered by Google App Engine
This is Rietveld 408576698