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