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/browser/speech/input_tag_speech_dispatcher_host.h"
6
7#include "base/bind.h"
8#include "base/lazy_instance.h"
9#include "content/browser/browser_plugin/browser_plugin_guest.h"
10#include "content/browser/renderer_host/render_view_host_impl.h"
11#include "content/browser/speech/speech_recognition_manager_impl.h"
12#include "content/browser/web_contents/web_contents_impl.h"
13#include "content/common/speech_recognition_messages.h"
14#include "content/public/browser/speech_recognition_manager_delegate.h"
15#include "content/public/browser/speech_recognition_session_config.h"
16#include "content/public/browser/speech_recognition_session_context.h"
17
18namespace {
19const uint32 kMaxHypothesesForSpeechInputTag = 6;
20}
21
22namespace content {
23
24InputTagSpeechDispatcherHost::InputTagSpeechDispatcherHost(
25    bool is_guest,
26    int render_process_id,
27    net::URLRequestContextGetter* url_request_context_getter)
28    : is_guest_(is_guest),
29      render_process_id_(render_process_id),
30      url_request_context_getter_(url_request_context_getter),
31      weak_factory_(this) {
32  // Do not add any non-trivial initialization here, instead do it lazily when
33  // required (e.g. see the method |SpeechRecognitionManager::GetInstance()|) or
34  // add an Init() method.
35}
36
37InputTagSpeechDispatcherHost::~InputTagSpeechDispatcherHost() {
38  SpeechRecognitionManager::GetInstance()->AbortAllSessionsForRenderProcess(
39      render_process_id_);
40}
41
42base::WeakPtr<InputTagSpeechDispatcherHost>
43InputTagSpeechDispatcherHost::AsWeakPtr() {
44  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
45  return weak_factory_.GetWeakPtr();
46}
47
48bool InputTagSpeechDispatcherHost::OnMessageReceived(
49    const IPC::Message& message, bool* message_was_ok) {
50  bool handled = true;
51  IPC_BEGIN_MESSAGE_MAP_EX(InputTagSpeechDispatcherHost, message,
52                           *message_was_ok)
53    IPC_MESSAGE_HANDLER(InputTagSpeechHostMsg_StartRecognition,
54                        OnStartRecognition)
55    IPC_MESSAGE_HANDLER(InputTagSpeechHostMsg_CancelRecognition,
56                        OnCancelRecognition)
57    IPC_MESSAGE_HANDLER(InputTagSpeechHostMsg_StopRecording,
58                        OnStopRecording)
59    IPC_MESSAGE_UNHANDLED(handled = false)
60  IPC_END_MESSAGE_MAP()
61  return handled;
62}
63
64void InputTagSpeechDispatcherHost::OverrideThreadForMessage(
65    const IPC::Message& message,
66    BrowserThread::ID* thread) {
67  if (message.type() == InputTagSpeechHostMsg_StartRecognition::ID)
68    *thread = BrowserThread::UI;
69}
70
71void InputTagSpeechDispatcherHost::OnChannelClosing() {
72  weak_factory_.InvalidateWeakPtrs();
73}
74
75void InputTagSpeechDispatcherHost::OnStartRecognition(
76    const InputTagSpeechHostMsg_StartRecognition_Params& params) {
77  InputTagSpeechHostMsg_StartRecognition_Params input_params(params);
78  int render_process_id = render_process_id_;
79  // The chrome layer is mostly oblivious to BrowserPlugin guests and so it
80  // cannot correctly place the speech bubble relative to a guest. Thus, we
81  // set up the speech recognition context relative to the embedder.
82  int guest_render_view_id = MSG_ROUTING_NONE;
83  if (is_guest_) {
84    RenderViewHostImpl* render_view_host =
85        RenderViewHostImpl::FromID(render_process_id_, params.render_view_id);
86    WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
87        WebContents::FromRenderViewHost(render_view_host));
88    BrowserPluginGuest* guest = web_contents->GetBrowserPluginGuest();
89    input_params.element_rect.set_origin(
90        guest->GetScreenCoordinates(input_params.element_rect.origin()));
91    guest_render_view_id = params.render_view_id;
92    render_process_id =
93        guest->embedder_web_contents()->GetRenderProcessHost()->GetID();
94    input_params.render_view_id =
95        guest->embedder_web_contents()->GetRoutingID();
96  }
97  bool filter_profanities =
98      SpeechRecognitionManagerImpl::GetInstance() &&
99      SpeechRecognitionManagerImpl::GetInstance()->delegate() &&
100      SpeechRecognitionManagerImpl::GetInstance()->delegate()->
101          FilterProfanities(render_process_id_);
102
103 BrowserThread::PostTask(
104      BrowserThread::IO, FROM_HERE,
105      base::Bind(
106          &InputTagSpeechDispatcherHost::StartRecognitionOnIO,
107          this,
108          render_process_id,
109          guest_render_view_id,
110          input_params,
111          filter_profanities));
112}
113
114void InputTagSpeechDispatcherHost::StartRecognitionOnIO(
115    int render_process_id,
116    int guest_render_view_id,
117    const InputTagSpeechHostMsg_StartRecognition_Params& params,
118    bool filter_profanities) {
119  SpeechRecognitionSessionContext context;
120  context.render_process_id = render_process_id;
121  context.render_view_id = params.render_view_id;
122  context.guest_render_view_id = guest_render_view_id;
123  // Keep context.embedder_render_process_id and context.embedder_render_view_id
124  // unset.
125  context.request_id = params.request_id;
126  context.element_rect = params.element_rect;
127
128  SpeechRecognitionSessionConfig config;
129  config.language = params.language;
130  if (!params.grammar.empty()) {
131    config.grammars.push_back(SpeechRecognitionGrammar(params.grammar));
132  }
133  config.max_hypotheses = kMaxHypothesesForSpeechInputTag;
134  config.origin_url = params.origin_url;
135  config.initial_context = context;
136  config.url_request_context_getter = url_request_context_getter_.get();
137  config.filter_profanities = filter_profanities;
138  config.event_listener = AsWeakPtr();
139
140  int session_id = SpeechRecognitionManager::GetInstance()->CreateSession(
141      config);
142  DCHECK_NE(session_id, SpeechRecognitionManager::kSessionIDInvalid);
143  SpeechRecognitionManager::GetInstance()->StartSession(session_id);
144}
145
146void InputTagSpeechDispatcherHost::OnCancelRecognition(int render_view_id,
147                                                       int request_id) {
148  int session_id = SpeechRecognitionManager::GetInstance()->GetSession(
149      render_process_id_, render_view_id, request_id);
150
151  // The renderer might provide an invalid |request_id| if the session was not
152  // started as expected, e.g., due to unsatisfied security requirements.
153  if (session_id != SpeechRecognitionManager::kSessionIDInvalid)
154    SpeechRecognitionManager::GetInstance()->AbortSession(session_id);
155}
156
157void InputTagSpeechDispatcherHost::OnStopRecording(int render_view_id,
158                                                   int request_id) {
159  int session_id = SpeechRecognitionManager::GetInstance()->GetSession(
160      render_process_id_, render_view_id, request_id);
161
162  // The renderer might provide an invalid |request_id| if the session was not
163  // started as expected, e.g., due to unsatisfied security requirements.
164  if (session_id != SpeechRecognitionManager::kSessionIDInvalid) {
165    SpeechRecognitionManager::GetInstance()->StopAudioCaptureForSession(
166        session_id);
167  }
168}
169
170// -------- SpeechRecognitionEventListener interface implementation -----------
171void InputTagSpeechDispatcherHost::OnRecognitionResults(
172    int session_id,
173    const SpeechRecognitionResults& results) {
174  DVLOG(1) << "InputTagSpeechDispatcherHost::OnRecognitionResults enter";
175
176  const SpeechRecognitionSessionContext& context =
177      SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
178
179  int render_view_id =
180      context.guest_render_view_id == MSG_ROUTING_NONE ?
181          context.render_view_id : context.guest_render_view_id;
182  Send(new InputTagSpeechMsg_SetRecognitionResults(
183      render_view_id,
184      context.request_id,
185      results));
186  DVLOG(1) << "InputTagSpeechDispatcherHost::OnRecognitionResults exit";
187}
188
189void InputTagSpeechDispatcherHost::OnAudioEnd(int session_id) {
190  DVLOG(1) << "InputTagSpeechDispatcherHost::OnAudioEnd enter";
191
192  const SpeechRecognitionSessionContext& context =
193      SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
194  int render_view_id =
195      context.guest_render_view_id == MSG_ROUTING_NONE ?
196          context.render_view_id : context.guest_render_view_id;
197  Send(new InputTagSpeechMsg_RecordingComplete(render_view_id,
198                                               context.request_id));
199  DVLOG(1) << "InputTagSpeechDispatcherHost::OnAudioEnd exit";
200}
201
202void InputTagSpeechDispatcherHost::OnRecognitionEnd(int session_id) {
203  DVLOG(1) << "InputTagSpeechDispatcherHost::OnRecognitionEnd enter";
204  const SpeechRecognitionSessionContext& context =
205      SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
206  int render_view_id =
207      context.guest_render_view_id == MSG_ROUTING_NONE ?
208          context.render_view_id : context.guest_render_view_id;
209  Send(new InputTagSpeechMsg_RecognitionComplete(render_view_id,
210                                                 context.request_id));
211  DVLOG(1) << "InputTagSpeechDispatcherHost::OnRecognitionEnd exit";
212}
213
214// The events below are currently not used by x-webkit-speech implementation.
215void InputTagSpeechDispatcherHost::OnRecognitionStart(int session_id) {}
216void InputTagSpeechDispatcherHost::OnAudioStart(int session_id) {}
217void InputTagSpeechDispatcherHost::OnSoundStart(int session_id) {}
218void InputTagSpeechDispatcherHost::OnSoundEnd(int session_id) {}
219void InputTagSpeechDispatcherHost::OnRecognitionError(
220    int session_id,
221    const SpeechRecognitionError& error) {}
222void InputTagSpeechDispatcherHost::OnAudioLevelsChange(
223    int session_id, float volume, float noise_volume) {}
224void InputTagSpeechDispatcherHost::OnEnvironmentEstimationComplete(
225    int session_id) {}
226
227}  // namespace content
228