1// Copyright 2013 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/speech/chrome_speech_recognition_manager_delegate_bubble_ui.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "content/public/browser/browser_thread.h"
9#include "content/public/browser/speech_recognition_manager.h"
10#include "content/public/browser/speech_recognition_session_context.h"
11#include "content/public/common/speech_recognition_error.h"
12#include "grit/generated_resources.h"
13#include "ui/base/l10n/l10n_util.h"
14
15using content::BrowserThread;
16using content::SpeechRecognitionManager;
17
18namespace {
19
20bool RequiresBubble(int session_id) {
21  return SpeechRecognitionManager::GetInstance()->
22      GetSessionContext(session_id).requested_by_page_element;
23}
24
25}  // namespace
26
27namespace speech {
28
29ChromeSpeechRecognitionManagerDelegateBubbleUI
30::ChromeSpeechRecognitionManagerDelegateBubbleUI() {
31}
32
33ChromeSpeechRecognitionManagerDelegateBubbleUI
34::~ChromeSpeechRecognitionManagerDelegateBubbleUI() {
35  if (bubble_controller_.get())
36    bubble_controller_->CloseBubble();
37}
38
39void ChromeSpeechRecognitionManagerDelegateBubbleUI::InfoBubbleButtonClicked(
40    int session_id, SpeechRecognitionBubble::Button button) {
41  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
42
43  // Note, the session might have been destroyed, therefore avoid calls to the
44  // manager which imply its existance (e.g., GetSessionContext()).
45
46  if (button == SpeechRecognitionBubble::BUTTON_CANCEL) {
47    GetBubbleController()->CloseBubble();
48    last_session_config_.reset();
49
50    // We can safely call AbortSession even if the session has already ended,
51    // the manager's public methods are reliable and will handle it properly.
52    SpeechRecognitionManager::GetInstance()->AbortSession(session_id);
53  } else if (button == SpeechRecognitionBubble::BUTTON_TRY_AGAIN) {
54    GetBubbleController()->CloseBubble();
55    RestartLastSession();
56  } else {
57    NOTREACHED();
58  }
59}
60
61void ChromeSpeechRecognitionManagerDelegateBubbleUI::InfoBubbleFocusChanged(
62    int session_id) {
63  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
64
65  // This check is needed since on some systems (MacOS), in rare cases, if the
66  // user clicks repeatedly and fast on the input element, the FocusChanged
67  // event (corresponding to the old session that should be aborted) can be
68  // received after a new session (corresponding to the 2nd click) is started.
69  if (GetBubbleController()->GetActiveSessionID() != session_id)
70    return;
71
72  // Note, the session might have been destroyed, therefore avoid calls to the
73  // manager which imply its existance (e.g., GetSessionContext()).
74  GetBubbleController()->CloseBubble();
75  last_session_config_.reset();
76
77  // Clicking outside the bubble means we should abort.
78  SpeechRecognitionManager::GetInstance()->AbortSession(session_id);
79}
80
81void ChromeSpeechRecognitionManagerDelegateBubbleUI::OnRecognitionStart(
82    int session_id) {
83  ChromeSpeechRecognitionManagerDelegate::OnRecognitionStart(session_id);
84
85  const content::SpeechRecognitionSessionContext& context =
86      SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
87
88  if (RequiresBubble(session_id)) {
89    // Copy the configuration of the session (for the "try again" button).
90    last_session_config_.reset(new content::SpeechRecognitionSessionConfig(
91        SpeechRecognitionManager::GetInstance()->GetSessionConfig(session_id)));
92
93    // Create and show the bubble. It will be closed upon the OnEnd event.
94    GetBubbleController()->CreateBubble(session_id,
95                                        context.render_process_id,
96                                        context.render_view_id,
97                                        context.element_rect);
98  }
99}
100
101void ChromeSpeechRecognitionManagerDelegateBubbleUI::OnAudioStart(
102    int session_id) {
103  ChromeSpeechRecognitionManagerDelegate::OnAudioStart(session_id);
104
105  if (RequiresBubble(session_id)) {
106    DCHECK_EQ(session_id, GetBubbleController()->GetActiveSessionID());
107    GetBubbleController()->SetBubbleRecordingMode();
108  }
109}
110
111void ChromeSpeechRecognitionManagerDelegateBubbleUI::OnAudioEnd(
112    int session_id) {
113  ChromeSpeechRecognitionManagerDelegate::OnAudioEnd(session_id);
114
115  // OnAudioEnd can be also raised after an abort, when the bubble has already
116  // been closed.
117  if (GetBubbleController()->GetActiveSessionID() == session_id) {
118    DCHECK(RequiresBubble(session_id));
119    GetBubbleController()->SetBubbleRecognizingMode();
120  }
121}
122
123void ChromeSpeechRecognitionManagerDelegateBubbleUI::OnRecognitionError(
124    int session_id, const content::SpeechRecognitionError& error) {
125  ChromeSpeechRecognitionManagerDelegate::OnRecognitionError(session_id, error);
126
127  // An error can be dispatched when the bubble is not visible anymore.
128  if (GetBubbleController()->GetActiveSessionID() != session_id)
129    return;
130  DCHECK(RequiresBubble(session_id));
131
132  int error_message_id = 0;
133  switch (error.code) {
134    case content::SPEECH_RECOGNITION_ERROR_AUDIO:
135      switch (error.details) {
136        case content::SPEECH_AUDIO_ERROR_DETAILS_NO_MIC:
137          error_message_id = IDS_SPEECH_INPUT_NO_MIC;
138          break;
139        default:
140          error_message_id = IDS_SPEECH_INPUT_MIC_ERROR;
141          break;
142      }
143      break;
144    case content::SPEECH_RECOGNITION_ERROR_ABORTED:
145      error_message_id = IDS_SPEECH_INPUT_ABORTED;
146      break;
147    case content::SPEECH_RECOGNITION_ERROR_NO_SPEECH:
148      error_message_id = IDS_SPEECH_INPUT_NO_SPEECH;
149      break;
150    case content::SPEECH_RECOGNITION_ERROR_NO_MATCH:
151      error_message_id = IDS_SPEECH_INPUT_NO_RESULTS;
152      break;
153    case content::SPEECH_RECOGNITION_ERROR_NETWORK:
154      error_message_id = IDS_SPEECH_INPUT_NET_ERROR;
155      break;
156    default:
157      NOTREACHED() << "unknown error " << error.code;
158      return;
159  }
160  GetBubbleController()->SetBubbleMessage(
161      l10n_util::GetStringUTF16(error_message_id));
162
163}
164
165void ChromeSpeechRecognitionManagerDelegateBubbleUI::OnAudioLevelsChange(
166    int session_id, float volume, float noise_volume) {
167  ChromeSpeechRecognitionManagerDelegate::OnAudioLevelsChange(
168      session_id, volume, noise_volume);
169
170  if (GetBubbleController()->GetActiveSessionID() == session_id) {
171    DCHECK(RequiresBubble(session_id));
172    GetBubbleController()->SetBubbleInputVolume(volume, noise_volume);
173  }
174}
175
176void ChromeSpeechRecognitionManagerDelegateBubbleUI::OnRecognitionEnd(
177    int session_id) {
178  ChromeSpeechRecognitionManagerDelegate::OnRecognitionEnd(session_id);
179
180  // The only case in which the OnRecognitionEnd should not close the bubble is
181  // when we are showing an error. In this case the bubble will be closed by
182  // the |InfoBubbleFocusChanged| method, when the users clicks either the
183  // "Cancel" button or outside of the bubble.
184  if (GetBubbleController()->GetActiveSessionID() == session_id &&
185      !GetBubbleController()->IsShowingMessage()) {
186    DCHECK(RequiresBubble(session_id));
187    GetBubbleController()->CloseBubble();
188  }
189}
190
191void ChromeSpeechRecognitionManagerDelegateBubbleUI::TabClosedCallback(
192    int render_process_id, int render_view_id) {
193  ChromeSpeechRecognitionManagerDelegate::TabClosedCallback(
194      render_process_id, render_view_id);
195
196  if (bubble_controller_.get() &&
197      bubble_controller_->IsShowingBubbleForRenderView(render_process_id,
198                                                       render_view_id)) {
199    bubble_controller_->CloseBubble();
200  }
201}
202
203SpeechRecognitionBubbleController*
204ChromeSpeechRecognitionManagerDelegateBubbleUI::GetBubbleController() {
205  if (!bubble_controller_.get())
206    bubble_controller_ = new SpeechRecognitionBubbleController(this);
207  return bubble_controller_.get();
208}
209
210void ChromeSpeechRecognitionManagerDelegateBubbleUI::RestartLastSession() {
211  DCHECK(last_session_config_.get());
212  SpeechRecognitionManager* manager = SpeechRecognitionManager::GetInstance();
213  const int new_session_id = manager->CreateSession(*last_session_config_);
214  DCHECK_NE(SpeechRecognitionManager::kSessionIDInvalid, new_session_id);
215  last_session_config_.reset();
216  manager->StartSession(new_session_id);
217}
218
219}  // namespace speech
220