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
15 namespace speech_input {
16
SpeechInputBubbleController(Delegate * delegate)17 SpeechInputBubbleController::SpeechInputBubbleController(Delegate* delegate)
18 : delegate_(delegate),
19 current_bubble_caller_id_(0),
20 registrar_(new NotificationRegistrar) {
21 }
22
~SpeechInputBubbleController()23 SpeechInputBubbleController::~SpeechInputBubbleController() {
24 DCHECK(bubbles_.empty());
25 }
26
CreateBubble(int caller_id,int render_process_id,int render_view_id,const gfx::Rect & element_rect)27 void 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
CloseBubble(int caller_id)54 void SpeechInputBubbleController::CloseBubble(int caller_id) {
55 ProcessRequestInUiThread(caller_id, REQUEST_CLOSE, string16(), 0, 0);
56 }
57
SetBubbleWarmUpMode(int caller_id)58 void SpeechInputBubbleController::SetBubbleWarmUpMode(int caller_id) {
59 ProcessRequestInUiThread(caller_id, REQUEST_SET_WARM_UP_MODE,
60 string16(), 0, 0);
61 }
62
SetBubbleRecordingMode(int caller_id)63 void SpeechInputBubbleController::SetBubbleRecordingMode(int caller_id) {
64 ProcessRequestInUiThread(caller_id, REQUEST_SET_RECORDING_MODE,
65 string16(), 0, 0);
66 }
67
SetBubbleRecognizingMode(int caller_id)68 void SpeechInputBubbleController::SetBubbleRecognizingMode(int caller_id) {
69 ProcessRequestInUiThread(caller_id, REQUEST_SET_RECOGNIZING_MODE,
70 string16(), 0, 0);
71 }
72
SetBubbleInputVolume(int caller_id,float volume,float noise_volume)73 void 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
SetBubbleMessage(int caller_id,const string16 & text)80 void SpeechInputBubbleController::SetBubbleMessage(int caller_id,
81 const string16& text) {
82 ProcessRequestInUiThread(caller_id, REQUEST_SET_MESSAGE, text, 0, 0);
83 }
84
UpdateTabContentsSubscription(int caller_id,ManageSubscriptionAction action)85 void 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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)112 void 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
ProcessRequestInUiThread(int caller_id,RequestType type,const string16 & text,float volume,float noise_volume)139 void 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
InfoBubbleButtonClicked(SpeechInputBubble::Button button)195 void 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
InfoBubbleFocusChanged()208 void 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
InvokeDelegateButtonClicked(int caller_id,SpeechInputBubble::Button button)223 void SpeechInputBubbleController::InvokeDelegateButtonClicked(
224 int caller_id, SpeechInputBubble::Button button) {
225 delegate_->InfoBubbleButtonClicked(caller_id, button);
226 }
227
InvokeDelegateFocusChanged(int caller_id)228 void SpeechInputBubbleController::InvokeDelegateFocusChanged(int caller_id) {
229 delegate_->InfoBubbleFocusChanged(caller_id);
230 }
231
232 } // namespace speech_input
233