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/speech/speech_input_bubble_controller.h"
6
7#include "chrome/browser/tab_contents/tab_util.h"
8#include "content/browser/browser_thread.h"
9#include "content/browser/tab_contents/tab_contents.h"
10#include "content/common/notification_registrar.h"
11#include "content/common/notification_source.h"
12#include "content/common/notification_type.h"
13#include "ui/gfx/rect.h"
14
15namespace speech_input {
16
17SpeechInputBubbleController::SpeechInputBubbleController(Delegate* delegate)
18    : delegate_(delegate),
19      current_bubble_caller_id_(0),
20      registrar_(new NotificationRegistrar) {
21}
22
23SpeechInputBubbleController::~SpeechInputBubbleController() {
24  DCHECK(bubbles_.empty());
25}
26
27void SpeechInputBubbleController::CreateBubble(int caller_id,
28                                               int render_process_id,
29                                               int render_view_id,
30                                               const gfx::Rect& element_rect) {
31  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
32    BrowserThread::PostTask(
33        BrowserThread::UI, FROM_HERE,
34        NewRunnableMethod(this, &SpeechInputBubbleController::CreateBubble,
35                          caller_id, render_process_id, render_view_id,
36                          element_rect));
37    return;
38  }
39  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
40  TabContents* tab_contents = tab_util::GetTabContentsByID(render_process_id,
41                                                           render_view_id);
42
43  DCHECK_EQ(0u, bubbles_.count(caller_id));
44  SpeechInputBubble* bubble = SpeechInputBubble::Create(tab_contents, this,
45                                                        element_rect);
46  if (!bubble)  // could be null if tab or display rect were invalid.
47    return;
48
49  bubbles_[caller_id] = bubble;
50
51  UpdateTabContentsSubscription(caller_id, BUBBLE_ADDED);
52}
53
54void SpeechInputBubbleController::CloseBubble(int caller_id) {
55  ProcessRequestInUiThread(caller_id, REQUEST_CLOSE, string16(), 0, 0);
56}
57
58void SpeechInputBubbleController::SetBubbleWarmUpMode(int caller_id) {
59  ProcessRequestInUiThread(caller_id, REQUEST_SET_WARM_UP_MODE,
60                           string16(), 0, 0);
61}
62
63void SpeechInputBubbleController::SetBubbleRecordingMode(int caller_id) {
64  ProcessRequestInUiThread(caller_id, REQUEST_SET_RECORDING_MODE,
65                           string16(), 0, 0);
66}
67
68void SpeechInputBubbleController::SetBubbleRecognizingMode(int caller_id) {
69  ProcessRequestInUiThread(caller_id, REQUEST_SET_RECOGNIZING_MODE,
70                           string16(), 0, 0);
71}
72
73void SpeechInputBubbleController::SetBubbleInputVolume(int caller_id,
74                                                       float volume,
75                                                       float noise_volume) {
76  ProcessRequestInUiThread(caller_id, REQUEST_SET_INPUT_VOLUME, string16(),
77                           volume, noise_volume);
78}
79
80void SpeechInputBubbleController::SetBubbleMessage(int caller_id,
81                                                   const string16& text) {
82  ProcessRequestInUiThread(caller_id, REQUEST_SET_MESSAGE, text, 0, 0);
83}
84
85void SpeechInputBubbleController::UpdateTabContentsSubscription(
86    int caller_id, ManageSubscriptionAction action) {
87  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
88
89  // If there are any other bubbles existing for the same TabContents, we would
90  // have subscribed to tab close notifications on their behalf and we need to
91  // stay registered. So we don't change the subscription in such cases.
92  TabContents* tab_contents = bubbles_[caller_id]->tab_contents();
93  for (BubbleCallerIdMap::iterator iter = bubbles_.begin();
94       iter != bubbles_.end(); ++iter) {
95    if (iter->second->tab_contents() == tab_contents &&
96        iter->first != caller_id) {
97      // At least one other bubble exists for the same TabContents. So don't
98      // make any change to the subscription.
99      return;
100    }
101  }
102
103  if (action == BUBBLE_ADDED) {
104    registrar_->Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
105                    Source<TabContents>(tab_contents));
106  } else {
107    registrar_->Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
108                    Source<TabContents>(tab_contents));
109  }
110}
111
112void SpeechInputBubbleController::Observe(NotificationType type,
113                                          const NotificationSource& source,
114                                          const NotificationDetails& details) {
115  if (type == NotificationType::TAB_CONTENTS_DESTROYED) {
116    // Cancel all bubbles and active recognition sessions for this tab.
117    TabContents* tab_contents = Source<TabContents>(source).ptr();
118    BubbleCallerIdMap::iterator iter = bubbles_.begin();
119    while (iter != bubbles_.end()) {
120      if (iter->second->tab_contents() == tab_contents) {
121        BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
122            NewRunnableMethod(
123                this,
124                &SpeechInputBubbleController::InvokeDelegateButtonClicked,
125                iter->first, SpeechInputBubble::BUTTON_CANCEL));
126        CloseBubble(iter->first);
127        // We expect to have a very small number of items in this map so
128        // redo-ing from start is ok.
129        iter = bubbles_.begin();
130      } else {
131        ++iter;
132      }
133    }
134  } else {
135    NOTREACHED() << "Unknown notification";
136  }
137}
138
139void SpeechInputBubbleController::ProcessRequestInUiThread(
140    int caller_id, RequestType type, const string16& text, float volume,
141    float noise_volume) {
142  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
143    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(
144        this, &SpeechInputBubbleController::ProcessRequestInUiThread,
145        caller_id, type, text, volume, noise_volume));
146    return;
147  }
148  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
149  // The bubble may have been closed before we got a chance to process this
150  // request. So check before proceeding.
151  if (!bubbles_.count(caller_id))
152    return;
153
154  bool change_active_bubble = (type == REQUEST_SET_WARM_UP_MODE ||
155                               type == REQUEST_SET_MESSAGE);
156  if (change_active_bubble) {
157    if (current_bubble_caller_id_ && current_bubble_caller_id_ != caller_id)
158      bubbles_[current_bubble_caller_id_]->Hide();
159    current_bubble_caller_id_ = caller_id;
160  }
161
162  SpeechInputBubble* bubble = bubbles_[caller_id];
163  switch (type) {
164    case REQUEST_SET_WARM_UP_MODE:
165      bubble->SetWarmUpMode();
166      break;
167    case REQUEST_SET_RECORDING_MODE:
168      bubble->SetRecordingMode();
169      break;
170    case REQUEST_SET_RECOGNIZING_MODE:
171      bubble->SetRecognizingMode();
172      break;
173    case REQUEST_SET_MESSAGE:
174      bubble->SetMessage(text);
175      break;
176    case REQUEST_SET_INPUT_VOLUME:
177      bubble->SetInputVolume(volume, noise_volume);
178      break;
179    case REQUEST_CLOSE:
180      if (current_bubble_caller_id_ == caller_id)
181        current_bubble_caller_id_ = 0;
182      UpdateTabContentsSubscription(caller_id, BUBBLE_REMOVED);
183      delete bubble;
184      bubbles_.erase(caller_id);
185      break;
186    default:
187      NOTREACHED();
188      break;
189  }
190
191  if (change_active_bubble)
192    bubble->Show();
193}
194
195void SpeechInputBubbleController::InfoBubbleButtonClicked(
196    SpeechInputBubble::Button button) {
197  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
198  DCHECK(current_bubble_caller_id_);
199
200  BrowserThread::PostTask(
201      BrowserThread::IO, FROM_HERE,
202      NewRunnableMethod(
203          this,
204          &SpeechInputBubbleController::InvokeDelegateButtonClicked,
205          current_bubble_caller_id_, button));
206}
207
208void SpeechInputBubbleController::InfoBubbleFocusChanged() {
209  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
210  DCHECK(current_bubble_caller_id_);
211
212  int old_bubble_caller_id = current_bubble_caller_id_;
213  current_bubble_caller_id_ = 0;
214
215  BrowserThread::PostTask(
216      BrowserThread::IO, FROM_HERE,
217      NewRunnableMethod(
218          this,
219          &SpeechInputBubbleController::InvokeDelegateFocusChanged,
220          old_bubble_caller_id));
221}
222
223void SpeechInputBubbleController::InvokeDelegateButtonClicked(
224    int caller_id, SpeechInputBubble::Button button) {
225  delegate_->InfoBubbleButtonClicked(caller_id, button);
226}
227
228void SpeechInputBubbleController::InvokeDelegateFocusChanged(int caller_id) {
229  delegate_->InfoBubbleFocusChanged(caller_id);
230}
231
232}  // namespace speech_input
233