OLD | NEW |
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/speech/chrome_speech_input_manager.h" | 5 #include "chrome/browser/speech/chrome_speech_input_manager.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 | 8 |
9 #include "base/synchronization/lock.h" | 9 #include "base/synchronization/lock.h" |
10 #include "base/threading/thread_restrictions.h" | 10 #include "base/threading/thread_restrictions.h" |
11 #include "base/utf_string_conversions.h" | 11 #include "base/utf_string_conversions.h" |
12 #include "chrome/browser/browser_process.h" | 12 #include "chrome/browser/browser_process.h" |
13 #include "chrome/browser/prefs/pref_service.h" | 13 #include "chrome/browser/prefs/pref_service.h" |
14 #include "chrome/browser/tab_contents/tab_util.h" | 14 #include "chrome/browser/tab_contents/tab_util.h" |
15 #include "chrome/common/chrome_switches.h" | |
16 #include "chrome/common/pref_names.h" | 15 #include "chrome/common/pref_names.h" |
17 #include "content/browser/browser_thread.h" | 16 #include "content/browser/browser_thread.h" |
18 #include "grit/generated_resources.h" | 17 #include "grit/generated_resources.h" |
19 #include "media/audio/audio_manager.h" | 18 #include "media/audio/audio_manager.h" |
20 #include "ui/base/l10n/l10n_util.h" | 19 #include "ui/base/l10n/l10n_util.h" |
21 | 20 |
22 #if defined(OS_WIN) | 21 #if defined(OS_WIN) |
23 #include "chrome/installer/util/wmi.h" | 22 #include "chrome/installer/util/wmi.h" |
24 #endif | 23 #endif |
25 | 24 |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
70 base::AutoLock lock(lock_); | 69 base::AutoLock lock(lock_); |
71 return value_; | 70 return value_; |
72 } | 71 } |
73 | 72 |
74 bool can_report_metrics() { | 73 bool can_report_metrics() { |
75 base::AutoLock lock(lock_); | 74 base::AutoLock lock(lock_); |
76 return can_report_metrics_; | 75 return can_report_metrics_; |
77 } | 76 } |
78 | 77 |
79 private: | 78 private: |
| 79 friend class base::RefCountedThreadSafe<OptionalRequestInfo>; |
| 80 |
| 81 ~OptionalRequestInfo() {} |
| 82 |
80 base::Lock lock_; | 83 base::Lock lock_; |
81 std::string value_; | 84 std::string value_; |
82 bool can_report_metrics_; | 85 bool can_report_metrics_; |
83 | 86 |
84 DISALLOW_COPY_AND_ASSIGN(OptionalRequestInfo); | 87 DISALLOW_COPY_AND_ASSIGN(OptionalRequestInfo); |
85 }; | 88 }; |
86 | 89 |
87 ChromeSpeechInputManager::SpeechInputRequest::SpeechInputRequest() | 90 ChromeSpeechInputManager::SpeechInputRequest::SpeechInputRequest() |
88 : delegate(NULL), | 91 : delegate(NULL), |
89 is_active(false) { | 92 is_active(false) { |
90 } | 93 } |
91 | 94 |
92 ChromeSpeechInputManager::SpeechInputRequest::~SpeechInputRequest() { | 95 ChromeSpeechInputManager::SpeechInputRequest::~SpeechInputRequest() { |
93 } | 96 } |
94 | 97 |
95 ChromeSpeechInputManager* ChromeSpeechInputManager::GetInstance() { | 98 ChromeSpeechInputManager* ChromeSpeechInputManager::GetInstance() { |
96 return Singleton<ChromeSpeechInputManager>::get(); | 99 return Singleton<ChromeSpeechInputManager>::get(); |
97 } | 100 } |
98 | 101 |
99 ChromeSpeechInputManager::ChromeSpeechInputManager() | 102 ChromeSpeechInputManager::ChromeSpeechInputManager() |
100 : recording_caller_id_(0), | 103 : bubble_controller_(new SpeechInputBubbleController( |
101 bubble_controller_(new SpeechInputBubbleController( | |
102 ALLOW_THIS_IN_INITIALIZER_LIST(this))) { | 104 ALLOW_THIS_IN_INITIALIZER_LIST(this))) { |
103 } | 105 } |
104 | 106 |
105 ChromeSpeechInputManager::~ChromeSpeechInputManager() { | 107 ChromeSpeechInputManager::~ChromeSpeechInputManager() { |
106 while (requests_.begin() != requests_.end()) | |
107 CancelRecognition(requests_.begin()->first); | |
108 } | 108 } |
109 | 109 |
110 bool ChromeSpeechInputManager::HasPendingRequest(int caller_id) const { | 110 void ChromeSpeechInputManager::ShowRecognitionRequested( |
111 return requests_.find(caller_id) != requests_.end(); | |
112 } | |
113 | |
114 SpeechInputManagerDelegate* ChromeSpeechInputManager::GetDelegate( | |
115 int caller_id) const { | |
116 return requests_.find(caller_id)->second.delegate; | |
117 } | |
118 | |
119 void ChromeSpeechInputManager::StartRecognition( | |
120 SpeechInputManagerDelegate* delegate, | |
121 int caller_id, | 111 int caller_id, |
122 int render_process_id, | 112 int render_process_id, |
123 int render_view_id, | 113 int render_view_id, |
124 const gfx::Rect& element_rect, | 114 const gfx::Rect& element_rect) { |
125 const std::string& language, | 115 bubble_controller_->CreateBubble(caller_id, render_process_id, |
126 const std::string& grammar, | 116 render_view_id, element_rect); |
127 const std::string& origin_url) { | 117 } |
128 DCHECK(!HasPendingRequest(caller_id)); | |
129 | 118 |
130 bubble_controller_->CreateBubble(caller_id, render_process_id, render_view_id, | 119 void ChromeSpeechInputManager::GetRequestInfo( |
131 element_rect); | 120 bool* can_report_metrics, std::string* request_info) { |
132 | |
133 if (!optional_request_info_.get()) { | 121 if (!optional_request_info_.get()) { |
134 optional_request_info_ = new OptionalRequestInfo(); | 122 optional_request_info_ = new OptionalRequestInfo(); |
135 // Since hardware info is optional with speech input requests, we start an | 123 // Since hardware info is optional with speech input requests, we start an |
136 // asynchronous fetch here and move on with recording audio. This first | 124 // asynchronous fetch here and move on with recording audio. This first |
137 // speech input request would send an empty string for hardware info and | 125 // speech input request would send an empty string for hardware info and |
138 // subsequent requests may have the hardware info available if the fetch | 126 // subsequent requests may have the hardware info available if the fetch |
139 // completed before them. This way we don't end up stalling the user with | 127 // completed before them. This way we don't end up stalling the user with |
140 // a long wait and disk seeks when they click on a UI element and start | 128 // a long wait and disk seeks when they click on a UI element and start |
141 // speaking. | 129 // speaking. |
142 optional_request_info_->Refresh(); | 130 optional_request_info_->Refresh(); |
143 } | 131 } |
144 | 132 *can_report_metrics = optional_request_info_->can_report_metrics(); |
145 SpeechInputRequest* request = &requests_[caller_id]; | 133 *request_info = optional_request_info_->value(); |
146 request->delegate = delegate; | |
147 request->recognizer = new SpeechRecognizer( | |
148 this, caller_id, language, grammar, censor_results(), | |
149 optional_request_info_->value(), | |
150 optional_request_info_->can_report_metrics() ? origin_url : ""); | |
151 request->is_active = false; | |
152 | |
153 StartRecognitionForRequest(caller_id); | |
154 } | 134 } |
155 | 135 |
156 void ChromeSpeechInputManager::StartRecognitionForRequest(int caller_id) { | 136 void ChromeSpeechInputManager::ShowWarmUp(int caller_id) { |
157 DCHECK(HasPendingRequest(caller_id)); | 137 bubble_controller_->SetBubbleWarmUpMode(caller_id); |
158 | |
159 // If we are currently recording audio for another caller, abort that cleanly. | |
160 if (recording_caller_id_) | |
161 CancelRecognitionAndInformDelegate(recording_caller_id_); | |
162 | |
163 if (!AudioManager::GetAudioManager()->HasAudioInputDevices()) { | |
164 bubble_controller_->SetBubbleMessage( | |
165 caller_id, l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_NO_MIC)); | |
166 } else { | |
167 recording_caller_id_ = caller_id; | |
168 requests_[caller_id].is_active = true; | |
169 requests_[caller_id].recognizer->StartRecording(); | |
170 bubble_controller_->SetBubbleWarmUpMode(caller_id); | |
171 } | |
172 } | 138 } |
173 | 139 |
174 void ChromeSpeechInputManager::CancelRecognition(int caller_id) { | 140 void ChromeSpeechInputManager::ShowRecognizing(int caller_id) { |
175 DCHECK(HasPendingRequest(caller_id)); | |
176 if (requests_[caller_id].is_active) | |
177 requests_[caller_id].recognizer->CancelRecognition(); | |
178 requests_.erase(caller_id); | |
179 if (recording_caller_id_ == caller_id) | |
180 recording_caller_id_ = 0; | |
181 bubble_controller_->CloseBubble(caller_id); | |
182 } | |
183 | |
184 void ChromeSpeechInputManager::CancelAllRequestsWithDelegate( | |
185 SpeechInputManagerDelegate* delegate) { | |
186 SpeechRecognizerMap::iterator it = requests_.begin(); | |
187 while (it != requests_.end()) { | |
188 if (it->second.delegate == delegate) { | |
189 CancelRecognition(it->first); | |
190 // This map will have very few elements so it is simpler to restart. | |
191 it = requests_.begin(); | |
192 } else { | |
193 ++it; | |
194 } | |
195 } | |
196 } | |
197 | |
198 void ChromeSpeechInputManager::StopRecording(int caller_id) { | |
199 DCHECK(HasPendingRequest(caller_id)); | |
200 requests_[caller_id].recognizer->StopRecording(); | |
201 } | |
202 | |
203 void ChromeSpeechInputManager::SetRecognitionResult( | |
204 int caller_id, bool error, const SpeechInputResultArray& result) { | |
205 DCHECK(HasPendingRequest(caller_id)); | |
206 GetDelegate(caller_id)->SetRecognitionResult(caller_id, result); | |
207 } | |
208 | |
209 void ChromeSpeechInputManager::DidCompleteRecording(int caller_id) { | |
210 DCHECK(recording_caller_id_ == caller_id); | |
211 DCHECK(HasPendingRequest(caller_id)); | |
212 recording_caller_id_ = 0; | |
213 GetDelegate(caller_id)->DidCompleteRecording(caller_id); | |
214 bubble_controller_->SetBubbleRecognizingMode(caller_id); | 141 bubble_controller_->SetBubbleRecognizingMode(caller_id); |
215 } | 142 } |
216 | 143 |
217 void ChromeSpeechInputManager::DidCompleteRecognition(int caller_id) { | 144 void ChromeSpeechInputManager::ShowRecording(int caller_id) { |
218 GetDelegate(caller_id)->DidCompleteRecognition(caller_id); | 145 bubble_controller_->SetBubbleRecordingMode(caller_id); |
219 requests_.erase(caller_id); | |
220 bubble_controller_->CloseBubble(caller_id); | |
221 } | 146 } |
222 | 147 |
223 void ChromeSpeechInputManager::OnRecognizerError( | 148 void ChromeSpeechInputManager::ShowInputVolume( |
| 149 int caller_id, float volume, float noise_volume) { |
| 150 bubble_controller_->SetBubbleInputVolume(caller_id, volume, noise_volume); |
| 151 } |
| 152 |
| 153 void ChromeSpeechInputManager::ShowNoMicError(int caller_id) { |
| 154 bubble_controller_->SetBubbleMessage( |
| 155 caller_id, l10n_util::GetStringUTF16(IDS_SPEECH_INPUT_NO_MIC)); |
| 156 } |
| 157 |
| 158 void ChromeSpeechInputManager::ShowRecognizerError( |
224 int caller_id, SpeechRecognizer::ErrorCode error) { | 159 int caller_id, SpeechRecognizer::ErrorCode error) { |
225 if (caller_id == recording_caller_id_) | |
226 recording_caller_id_ = 0; | |
227 | |
228 requests_[caller_id].is_active = false; | |
229 | |
230 struct ErrorMessageMapEntry { | 160 struct ErrorMessageMapEntry { |
231 SpeechRecognizer::ErrorCode error; | 161 SpeechRecognizer::ErrorCode error; |
232 int message_id; | 162 int message_id; |
233 }; | 163 }; |
234 ErrorMessageMapEntry error_message_map[] = { | 164 ErrorMessageMapEntry error_message_map[] = { |
235 { | 165 { |
236 SpeechRecognizer::RECOGNIZER_ERROR_CAPTURE, IDS_SPEECH_INPUT_MIC_ERROR | 166 SpeechRecognizer::RECOGNIZER_ERROR_CAPTURE, IDS_SPEECH_INPUT_MIC_ERROR |
237 }, { | 167 }, { |
238 SpeechRecognizer::RECOGNIZER_ERROR_NO_SPEECH, IDS_SPEECH_INPUT_NO_SPEECH | 168 SpeechRecognizer::RECOGNIZER_ERROR_NO_SPEECH, IDS_SPEECH_INPUT_NO_SPEECH |
239 }, { | 169 }, { |
240 SpeechRecognizer::RECOGNIZER_ERROR_NO_RESULTS, IDS_SPEECH_INPUT_NO_RESULTS | 170 SpeechRecognizer::RECOGNIZER_ERROR_NO_RESULTS, IDS_SPEECH_INPUT_NO_RESULTS |
241 }, { | 171 }, { |
242 SpeechRecognizer::RECOGNIZER_ERROR_NETWORK, IDS_SPEECH_INPUT_NET_ERROR | 172 SpeechRecognizer::RECOGNIZER_ERROR_NETWORK, IDS_SPEECH_INPUT_NET_ERROR |
243 } | 173 } |
244 }; | 174 }; |
245 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(error_message_map); ++i) { | 175 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(error_message_map); ++i) { |
246 if (error_message_map[i].error == error) { | 176 if (error_message_map[i].error == error) { |
247 bubble_controller_->SetBubbleMessage( | 177 bubble_controller_->SetBubbleMessage( |
248 caller_id, | 178 caller_id, |
249 l10n_util::GetStringUTF16(error_message_map[i].message_id)); | 179 l10n_util::GetStringUTF16(error_message_map[i].message_id)); |
250 return; | 180 return; |
251 } | 181 } |
252 } | 182 } |
253 | 183 |
254 NOTREACHED() << "unknown error " << error; | 184 NOTREACHED() << "unknown error " << error; |
255 } | 185 } |
256 | 186 |
257 void ChromeSpeechInputManager::DidStartReceivingAudio(int caller_id) { | 187 void ChromeSpeechInputManager::DoClose(int caller_id) { |
258 DCHECK(HasPendingRequest(caller_id)); | 188 bubble_controller_->CloseBubble(caller_id); |
259 DCHECK(recording_caller_id_ == caller_id); | |
260 bubble_controller_->SetBubbleRecordingMode(caller_id); | |
261 } | |
262 | |
263 void ChromeSpeechInputManager::DidCompleteEnvironmentEstimation(int caller_id) { | |
264 DCHECK(HasPendingRequest(caller_id)); | |
265 DCHECK(recording_caller_id_ == caller_id); | |
266 } | |
267 | |
268 void ChromeSpeechInputManager::SetInputVolume(int caller_id, float volume, | |
269 float noise_volume) { | |
270 DCHECK(HasPendingRequest(caller_id)); | |
271 DCHECK_EQ(recording_caller_id_, caller_id); | |
272 | |
273 bubble_controller_->SetBubbleInputVolume(caller_id, volume, noise_volume); | |
274 } | |
275 | |
276 void ChromeSpeechInputManager::CancelRecognitionAndInformDelegate( | |
277 int caller_id) { | |
278 SpeechInputManagerDelegate* cur_delegate = GetDelegate(caller_id); | |
279 CancelRecognition(caller_id); | |
280 cur_delegate->DidCompleteRecording(caller_id); | |
281 cur_delegate->DidCompleteRecognition(caller_id); | |
282 } | 189 } |
283 | 190 |
284 void ChromeSpeechInputManager::InfoBubbleButtonClicked( | 191 void ChromeSpeechInputManager::InfoBubbleButtonClicked( |
285 int caller_id, SpeechInputBubble::Button button) { | 192 int caller_id, SpeechInputBubble::Button button) { |
286 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
287 // Ignore if the caller id was not in our active recognizers list because the | 194 // Ignore if the caller id was not in our active recognizers list because the |
288 // user might have clicked more than once, or recognition could have been | 195 // user might have clicked more than once, or recognition could have been |
289 // cancelled due to other reasons before the user click was processed. | 196 // cancelled due to other reasons before the user click was processed. |
290 if (!HasPendingRequest(caller_id)) | 197 if (!HasPendingRequest(caller_id)) |
291 return; | 198 return; |
292 | 199 |
293 if (button == SpeechInputBubble::BUTTON_CANCEL) { | 200 if (button == SpeechInputBubble::BUTTON_CANCEL) { |
294 CancelRecognitionAndInformDelegate(caller_id); | 201 CancelRecognitionAndInformDelegate(caller_id); |
295 } else if (button == SpeechInputBubble::BUTTON_TRY_AGAIN) { | 202 } else if (button == SpeechInputBubble::BUTTON_TRY_AGAIN) { |
296 StartRecognitionForRequest(caller_id); | 203 StartRecognitionForRequest(caller_id); |
297 } | 204 } |
298 } | 205 } |
299 | 206 |
300 void ChromeSpeechInputManager::InfoBubbleFocusChanged(int caller_id) { | 207 void ChromeSpeechInputManager::InfoBubbleFocusChanged(int caller_id) { |
301 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 208 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
302 // Ignore if the caller id was not in our active recognizers list because the | 209 OnFocusChanged(caller_id); |
303 // user might have clicked more than once, or recognition could have been | |
304 // ended due to other reasons before the user click was processed. | |
305 if (HasPendingRequest(caller_id)) { | |
306 // If this is an ongoing recording or if we were displaying an error message | |
307 // to the user, abort it since user has switched focus. Otherwise | |
308 // recognition has started and keep that going so user can start speaking to | |
309 // another element while this gets the results in parallel. | |
310 if (recording_caller_id_ == caller_id || !requests_[caller_id].is_active) { | |
311 CancelRecognitionAndInformDelegate(caller_id); | |
312 } | |
313 } | |
314 } | 210 } |
315 | 211 |
316 } // namespace speech_input | 212 } // namespace speech_input |
OLD | NEW |