1// Copyright (c) 2012 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 "content/renderer/speech_recognition_dispatcher.h"
6
7#include "base/basictypes.h"
8#include "base/strings/utf_string_conversions.h"
9#include "content/common/speech_recognition_messages.h"
10#include "content/renderer/render_view_impl.h"
11#include "third_party/WebKit/public/platform/WebString.h"
12#include "third_party/WebKit/public/platform/WebVector.h"
13#include "third_party/WebKit/public/web/WebSpeechGrammar.h"
14#include "third_party/WebKit/public/web/WebSpeechRecognitionParams.h"
15#include "third_party/WebKit/public/web/WebSpeechRecognitionResult.h"
16#include "third_party/WebKit/public/web/WebSpeechRecognizerClient.h"
17
18using blink::WebVector;
19using blink::WebString;
20using blink::WebSpeechGrammar;
21using blink::WebSpeechRecognitionHandle;
22using blink::WebSpeechRecognitionResult;
23using blink::WebSpeechRecognitionParams;
24using blink::WebSpeechRecognizerClient;
25
26namespace content {
27
28SpeechRecognitionDispatcher::SpeechRecognitionDispatcher(
29    RenderViewImpl* render_view)
30    : RenderViewObserver(render_view),
31      recognizer_client_(NULL),
32      next_id_(1) {
33}
34
35SpeechRecognitionDispatcher::~SpeechRecognitionDispatcher() {
36}
37
38void SpeechRecognitionDispatcher::AbortAllRecognitions() {
39  Send(new SpeechRecognitionHostMsg_AbortAllRequests(
40      routing_id()));
41}
42
43bool SpeechRecognitionDispatcher::OnMessageReceived(
44    const IPC::Message& message) {
45  bool handled = true;
46  IPC_BEGIN_MESSAGE_MAP(SpeechRecognitionDispatcher, message)
47    IPC_MESSAGE_HANDLER(SpeechRecognitionMsg_Started, OnRecognitionStarted)
48    IPC_MESSAGE_HANDLER(SpeechRecognitionMsg_AudioStarted, OnAudioStarted)
49    IPC_MESSAGE_HANDLER(SpeechRecognitionMsg_SoundStarted, OnSoundStarted)
50    IPC_MESSAGE_HANDLER(SpeechRecognitionMsg_SoundEnded, OnSoundEnded)
51    IPC_MESSAGE_HANDLER(SpeechRecognitionMsg_AudioEnded, OnAudioEnded)
52    IPC_MESSAGE_HANDLER(SpeechRecognitionMsg_ErrorOccurred, OnErrorOccurred)
53    IPC_MESSAGE_HANDLER(SpeechRecognitionMsg_Ended, OnRecognitionEnded)
54    IPC_MESSAGE_HANDLER(SpeechRecognitionMsg_ResultRetrieved,
55                        OnResultsRetrieved)
56    IPC_MESSAGE_UNHANDLED(handled = false)
57  IPC_END_MESSAGE_MAP()
58  return handled;
59}
60
61void SpeechRecognitionDispatcher::start(
62    const WebSpeechRecognitionHandle& handle,
63    const WebSpeechRecognitionParams& params,
64    WebSpeechRecognizerClient* recognizer_client) {
65  DCHECK(!recognizer_client_ || recognizer_client_ == recognizer_client);
66  recognizer_client_ = recognizer_client;
67
68  SpeechRecognitionHostMsg_StartRequest_Params msg_params;
69  for (size_t i = 0; i < params.grammars().size(); ++i) {
70    const WebSpeechGrammar& grammar = params.grammars()[i];
71    msg_params.grammars.push_back(
72        SpeechRecognitionGrammar(grammar.src().spec(), grammar.weight()));
73  }
74  msg_params.language = base::UTF16ToUTF8(params.language());
75  msg_params.max_hypotheses = static_cast<uint32>(params.maxAlternatives());
76  msg_params.continuous = params.continuous();
77  msg_params.interim_results = params.interimResults();
78  msg_params.origin_url = params.origin().toString().utf8();
79  msg_params.render_view_id = routing_id();
80  msg_params.request_id = GetOrCreateIDForHandle(handle);
81  // The handle mapping will be removed in |OnRecognitionEnd|.
82  Send(new SpeechRecognitionHostMsg_StartRequest(msg_params));
83}
84
85void SpeechRecognitionDispatcher::stop(
86    const WebSpeechRecognitionHandle& handle,
87    WebSpeechRecognizerClient* recognizer_client) {
88  // Ignore a |stop| issued without a matching |start|.
89  if (recognizer_client_ != recognizer_client || !HandleExists(handle))
90    return;
91  Send(new SpeechRecognitionHostMsg_StopCaptureRequest(
92      routing_id(), GetOrCreateIDForHandle(handle)));
93}
94
95void SpeechRecognitionDispatcher::abort(
96    const WebSpeechRecognitionHandle& handle,
97    WebSpeechRecognizerClient* recognizer_client) {
98  // Ignore an |abort| issued without a matching |start|.
99  if (recognizer_client_ != recognizer_client || !HandleExists(handle))
100    return;
101  Send(new SpeechRecognitionHostMsg_AbortRequest(
102      routing_id(), GetOrCreateIDForHandle(handle)));
103}
104
105void SpeechRecognitionDispatcher::OnRecognitionStarted(int request_id) {
106  recognizer_client_->didStart(GetHandleFromID(request_id));
107}
108
109void SpeechRecognitionDispatcher::OnAudioStarted(int request_id) {
110  recognizer_client_->didStartAudio(GetHandleFromID(request_id));
111}
112
113void SpeechRecognitionDispatcher::OnSoundStarted(int request_id) {
114  recognizer_client_->didStartSound(GetHandleFromID(request_id));
115}
116
117void SpeechRecognitionDispatcher::OnSoundEnded(int request_id) {
118  recognizer_client_->didEndSound(GetHandleFromID(request_id));
119}
120
121void SpeechRecognitionDispatcher::OnAudioEnded(int request_id) {
122  recognizer_client_->didEndAudio(GetHandleFromID(request_id));
123}
124
125static WebSpeechRecognizerClient::ErrorCode WebKitErrorCode(
126    SpeechRecognitionErrorCode e) {
127  switch (e) {
128    case SPEECH_RECOGNITION_ERROR_NONE:
129      NOTREACHED();
130      return WebSpeechRecognizerClient::OtherError;
131    case SPEECH_RECOGNITION_ERROR_ABORTED:
132      return WebSpeechRecognizerClient::AbortedError;
133    case SPEECH_RECOGNITION_ERROR_AUDIO:
134      return WebSpeechRecognizerClient::AudioCaptureError;
135    case SPEECH_RECOGNITION_ERROR_NETWORK:
136      return WebSpeechRecognizerClient::NetworkError;
137    case SPEECH_RECOGNITION_ERROR_NOT_ALLOWED:
138      return WebSpeechRecognizerClient::NotAllowedError;
139    case SPEECH_RECOGNITION_ERROR_NO_SPEECH:
140      return WebSpeechRecognizerClient::NoSpeechError;
141    case SPEECH_RECOGNITION_ERROR_NO_MATCH:
142      NOTREACHED();
143      return WebSpeechRecognizerClient::OtherError;
144    case SPEECH_RECOGNITION_ERROR_BAD_GRAMMAR:
145      return WebSpeechRecognizerClient::BadGrammarError;
146  }
147  NOTREACHED();
148  return WebSpeechRecognizerClient::OtherError;
149}
150
151void SpeechRecognitionDispatcher::OnErrorOccurred(
152    int request_id, const SpeechRecognitionError& error) {
153  if (error.code == SPEECH_RECOGNITION_ERROR_NO_MATCH) {
154    recognizer_client_->didReceiveNoMatch(GetHandleFromID(request_id),
155                                          WebSpeechRecognitionResult());
156  } else {
157    recognizer_client_->didReceiveError(
158        GetHandleFromID(request_id),
159        WebString(),  // TODO(primiano): message?
160        WebKitErrorCode(error.code));
161  }
162}
163
164void SpeechRecognitionDispatcher::OnRecognitionEnded(int request_id) {
165  // TODO(tommi): It is possible that the handle isn't found in the array if
166  // the user just refreshed the page. It seems that we then get a notification
167  // for the previously loaded instance of the page.
168  HandleMap::iterator iter = handle_map_.find(request_id);
169  if (iter == handle_map_.end()) {
170    DLOG(ERROR) << "OnRecognitionEnded called for a handle that doesn't exist";
171  } else {
172    WebSpeechRecognitionHandle handle = iter->second;
173    // Note: we need to erase the handle from the map *before* calling didEnd.
174    // didEnd may call back synchronously to start a new recognition session,
175    // and we don't want to delete the handle from the map after that happens.
176    handle_map_.erase(request_id);
177    recognizer_client_->didEnd(handle);
178  }
179}
180
181void SpeechRecognitionDispatcher::OnResultsRetrieved(
182    int request_id, const SpeechRecognitionResults& results) {
183  size_t provisional_count = 0;
184  SpeechRecognitionResults::const_iterator it = results.begin();
185  for (; it != results.end(); ++it) {
186    if (it->is_provisional)
187      ++provisional_count;
188  }
189
190  WebVector<WebSpeechRecognitionResult> provisional(provisional_count);
191  WebVector<WebSpeechRecognitionResult> final(
192      results.size() - provisional_count);
193
194  int provisional_index = 0, final_index = 0;
195  for (it = results.begin(); it != results.end(); ++it) {
196    const SpeechRecognitionResult& result = (*it);
197    WebSpeechRecognitionResult* webkit_result = result.is_provisional ?
198        &provisional[provisional_index++] : &final[final_index++];
199
200    const size_t num_hypotheses = result.hypotheses.size();
201    WebVector<WebString> transcripts(num_hypotheses);
202    WebVector<float> confidences(num_hypotheses);
203    for (size_t i = 0; i < num_hypotheses; ++i) {
204      transcripts[i] = result.hypotheses[i].utterance;
205      confidences[i] = static_cast<float>(result.hypotheses[i].confidence);
206    }
207    webkit_result->assign(transcripts, confidences, !result.is_provisional);
208  }
209
210  recognizer_client_->didReceiveResults(
211      GetHandleFromID(request_id), final, provisional);
212}
213
214
215int SpeechRecognitionDispatcher::GetOrCreateIDForHandle(
216    const WebSpeechRecognitionHandle& handle) {
217  // Search first for an existing mapping.
218  for (HandleMap::iterator iter = handle_map_.begin();
219      iter != handle_map_.end();
220      ++iter) {
221    if (iter->second.equals(handle))
222      return iter->first;
223  }
224  // If no existing mapping found, create a new one.
225  const int new_id = next_id_;
226  handle_map_[new_id] = handle;
227  ++next_id_;
228  return new_id;
229}
230
231bool SpeechRecognitionDispatcher::HandleExists(
232    const WebSpeechRecognitionHandle& handle) {
233  for (HandleMap::iterator iter = handle_map_.begin();
234      iter != handle_map_.end();
235      ++iter) {
236    if (iter->second.equals(handle))
237      return true;
238  }
239  return false;
240}
241
242const WebSpeechRecognitionHandle& SpeechRecognitionDispatcher::GetHandleFromID(
243    int request_id) {
244  HandleMap::iterator iter = handle_map_.find(request_id);
245  DCHECK(iter != handle_map_.end());
246  return iter->second;
247}
248
249}  // namespace content
250