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

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

Issue 8386074: Add a tray notification UI for speech input recording in the extension API. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: creating the notification object dynamically on first use. Created 9 years, 1 month 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/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 }
OLDNEW
« no previous file with comments | « chrome/browser/extensions/speech_input/extension_speech_input_manager.h ('k') | chrome/browser/profiles/profile.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698