Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(320)

Side by Side Diff: chrome/browser/media/media_stream_capture_indicator.cc

Issue 2307083002: Cleanup: move WebRTC related files from chrome/browser/media to chrome/browser/media/webrtc/ (Closed)
Patch Set: Removed file wrongly resuscitated during rebase Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 "chrome/browser/media/media_stream_capture_indicator.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10 #include <string>
11 #include <utility>
12
13 #include "base/logging.h"
14 #include "base/macros.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/app/chrome_command_ids.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/status_icons/status_icon.h"
20 #include "chrome/browser/status_icons/status_tray.h"
21 #include "chrome/browser/tab_contents/tab_util.h"
22 #include "chrome/grit/chromium_strings.h"
23 #include "chrome/grit/theme_resources.h"
24 #include "components/url_formatter/elide_url.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/content_browser_client.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/web_contents_delegate.h"
29 #include "content/public/browser/web_contents_observer.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/gfx/image/image_skia.h"
33
34 #if defined(ENABLE_EXTENSIONS)
35 #include "chrome/common/extensions/extension_constants.h"
36 #include "extensions/browser/extension_registry.h"
37 #include "extensions/common/extension.h"
38 #endif
39
40 using content::BrowserThread;
41 using content::WebContents;
42
43 namespace {
44
45 #if defined(ENABLE_EXTENSIONS)
46 const extensions::Extension* GetExtension(WebContents* web_contents) {
47 DCHECK_CURRENTLY_ON(BrowserThread::UI);
48
49 if (!web_contents)
50 return NULL;
51
52 extensions::ExtensionRegistry* registry =
53 extensions::ExtensionRegistry::Get(web_contents->GetBrowserContext());
54 return registry->enabled_extensions().GetExtensionOrAppByURL(
55 web_contents->GetURL());
56 }
57
58 bool IsWhitelistedExtension(const extensions::Extension* extension) {
59 DCHECK_CURRENTLY_ON(BrowserThread::UI);
60
61 static const char* const kExtensionWhitelist[] = {
62 extension_misc::kHotwordNewExtensionId,
63 };
64
65 for (size_t i = 0; i < arraysize(kExtensionWhitelist); ++i) {
66 if (extension->id() == kExtensionWhitelist[i])
67 return true;
68 }
69
70 return false;
71 }
72 #endif // defined(ENABLE_EXTENSIONS)
73
74 base::string16 GetTitle(WebContents* web_contents) {
75 DCHECK_CURRENTLY_ON(BrowserThread::UI);
76
77 if (!web_contents)
78 return base::string16();
79
80 #if defined(ENABLE_EXTENSIONS)
81 const extensions::Extension* const extension = GetExtension(web_contents);
82 if (extension)
83 return base::UTF8ToUTF16(extension->name());
84 #endif
85
86 return url_formatter::FormatUrlForSecurityDisplay(web_contents->GetURL());
87 }
88
89 } // namespace
90
91 // Stores usage counts for all the capture devices associated with a single
92 // WebContents instance. Instances of this class are owned by
93 // MediaStreamCaptureIndicator. They also observe for the destruction of their
94 // corresponding WebContents and trigger their own deletion from their
95 // MediaStreamCaptureIndicator.
96 class MediaStreamCaptureIndicator::WebContentsDeviceUsage
97 : public content::WebContentsObserver {
98 public:
99 WebContentsDeviceUsage(scoped_refptr<MediaStreamCaptureIndicator> indicator,
100 WebContents* web_contents)
101 : WebContentsObserver(web_contents),
102 indicator_(indicator),
103 audio_ref_count_(0),
104 video_ref_count_(0),
105 mirroring_ref_count_(0),
106 weak_factory_(this) {
107 }
108
109 bool IsCapturingAudio() const { return audio_ref_count_ > 0; }
110 bool IsCapturingVideo() const { return video_ref_count_ > 0; }
111 bool IsMirroring() const { return mirroring_ref_count_ > 0; }
112
113 std::unique_ptr<content::MediaStreamUI> RegisterMediaStream(
114 const content::MediaStreamDevices& devices);
115
116 // Increment ref-counts up based on the type of each device provided.
117 void AddDevices(const content::MediaStreamDevices& devices);
118
119 // Decrement ref-counts up based on the type of each device provided.
120 void RemoveDevices(const content::MediaStreamDevices& devices);
121
122 private:
123 // content::WebContentsObserver overrides.
124 void WebContentsDestroyed() override {
125 indicator_->UnregisterWebContents(web_contents());
126 }
127
128 scoped_refptr<MediaStreamCaptureIndicator> indicator_;
129 int audio_ref_count_;
130 int video_ref_count_;
131 int mirroring_ref_count_;
132
133 base::WeakPtrFactory<WebContentsDeviceUsage> weak_factory_;
134
135 DISALLOW_COPY_AND_ASSIGN(WebContentsDeviceUsage);
136 };
137
138 // Implements MediaStreamUI interface. Instances of this class are created for
139 // each MediaStream and their ownership is passed to MediaStream implementation
140 // in the content layer. Each UIDelegate keeps a weak pointer to the
141 // corresponding WebContentsDeviceUsage object to deliver updates about state of
142 // the stream.
143 class MediaStreamCaptureIndicator::UIDelegate : public content::MediaStreamUI {
144 public:
145 UIDelegate(base::WeakPtr<WebContentsDeviceUsage> device_usage,
146 const content::MediaStreamDevices& devices)
147 : device_usage_(device_usage),
148 devices_(devices),
149 started_(false) {
150 DCHECK(!devices_.empty());
151 }
152
153 ~UIDelegate() override {
154 if (started_ && device_usage_.get())
155 device_usage_->RemoveDevices(devices_);
156 }
157
158 private:
159 // content::MediaStreamUI interface.
160 gfx::NativeViewId OnStarted(const base::Closure& close_callback) override {
161 DCHECK(!started_);
162 started_ = true;
163 if (device_usage_.get())
164 device_usage_->AddDevices(devices_);
165 return 0;
166 }
167
168 base::WeakPtr<WebContentsDeviceUsage> device_usage_;
169 content::MediaStreamDevices devices_;
170 bool started_;
171
172 DISALLOW_COPY_AND_ASSIGN(UIDelegate);
173 };
174
175 std::unique_ptr<content::MediaStreamUI>
176 MediaStreamCaptureIndicator::WebContentsDeviceUsage::RegisterMediaStream(
177 const content::MediaStreamDevices& devices) {
178 return base::WrapUnique(new UIDelegate(weak_factory_.GetWeakPtr(), devices));
179 }
180
181 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::AddDevices(
182 const content::MediaStreamDevices& devices) {
183 for (content::MediaStreamDevices::const_iterator it = devices.begin();
184 it != devices.end(); ++it) {
185 if (it->type == content::MEDIA_TAB_AUDIO_CAPTURE ||
186 it->type == content::MEDIA_TAB_VIDEO_CAPTURE) {
187 ++mirroring_ref_count_;
188 } else if (content::IsAudioInputMediaType(it->type)) {
189 ++audio_ref_count_;
190 } else if (content::IsVideoMediaType(it->type)) {
191 ++video_ref_count_;
192 } else {
193 NOTIMPLEMENTED();
194 }
195 }
196
197 if (web_contents())
198 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
199
200 indicator_->UpdateNotificationUserInterface();
201 }
202
203 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::RemoveDevices(
204 const content::MediaStreamDevices& devices) {
205 for (content::MediaStreamDevices::const_iterator it = devices.begin();
206 it != devices.end(); ++it) {
207 if (it->type == content::MEDIA_TAB_AUDIO_CAPTURE ||
208 it->type == content::MEDIA_TAB_VIDEO_CAPTURE) {
209 --mirroring_ref_count_;
210 } else if (content::IsAudioInputMediaType(it->type)) {
211 --audio_ref_count_;
212 } else if (content::IsVideoMediaType(it->type)) {
213 --video_ref_count_;
214 } else {
215 NOTIMPLEMENTED();
216 }
217 }
218
219 DCHECK_GE(audio_ref_count_, 0);
220 DCHECK_GE(video_ref_count_, 0);
221 DCHECK_GE(mirroring_ref_count_, 0);
222
223 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
224 indicator_->UpdateNotificationUserInterface();
225 }
226
227 MediaStreamCaptureIndicator::MediaStreamCaptureIndicator()
228 : status_icon_(NULL),
229 mic_image_(NULL),
230 camera_image_(NULL) {
231 }
232
233 MediaStreamCaptureIndicator::~MediaStreamCaptureIndicator() {
234 // The user is responsible for cleaning up by reporting the closure of any
235 // opened devices. However, there exists a race condition at shutdown: The UI
236 // thread may be stopped before CaptureDevicesClosed() posts the task to
237 // invoke DoDevicesClosedOnUIThread(). In this case, usage_map_ won't be
238 // empty like it should.
239 DCHECK(usage_map_.empty() ||
240 !BrowserThread::IsMessageLoopValid(BrowserThread::UI));
241 }
242
243 std::unique_ptr<content::MediaStreamUI>
244 MediaStreamCaptureIndicator::RegisterMediaStream(
245 content::WebContents* web_contents,
246 const content::MediaStreamDevices& devices) {
247 WebContentsDeviceUsage* usage = usage_map_.get(web_contents);
248 if (!usage) {
249 usage = new WebContentsDeviceUsage(this, web_contents);
250 usage_map_.add(web_contents, base::WrapUnique(usage));
251 }
252 return usage->RegisterMediaStream(devices);
253 }
254
255 void MediaStreamCaptureIndicator::ExecuteCommand(int command_id,
256 int event_flags) {
257 DCHECK_CURRENTLY_ON(BrowserThread::UI);
258
259 const int index =
260 command_id - IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
261 DCHECK_LE(0, index);
262 DCHECK_GT(static_cast<int>(command_targets_.size()), index);
263 WebContents* web_contents = command_targets_[index];
264 if (ContainsKey(usage_map_, web_contents))
265 web_contents->GetDelegate()->ActivateContents(web_contents);
266 }
267
268 bool MediaStreamCaptureIndicator::IsCapturingUserMedia(
269 content::WebContents* web_contents) const {
270 DCHECK_CURRENTLY_ON(BrowserThread::UI);
271
272 WebContentsDeviceUsage* usage = usage_map_.get(web_contents);
273 return usage && (usage->IsCapturingAudio() || usage->IsCapturingVideo());
274 }
275
276 bool MediaStreamCaptureIndicator::IsCapturingVideo(
277 content::WebContents* web_contents) const {
278 DCHECK_CURRENTLY_ON(BrowserThread::UI);
279
280 WebContentsDeviceUsage* usage = usage_map_.get(web_contents);
281 return usage && usage->IsCapturingVideo();
282 }
283
284 bool MediaStreamCaptureIndicator::IsCapturingAudio(
285 content::WebContents* web_contents) const {
286 DCHECK_CURRENTLY_ON(BrowserThread::UI);
287
288 WebContentsDeviceUsage* usage = usage_map_.get(web_contents);
289 return usage && usage->IsCapturingAudio();
290 }
291
292 bool MediaStreamCaptureIndicator::IsBeingMirrored(
293 content::WebContents* web_contents) const {
294 DCHECK_CURRENTLY_ON(BrowserThread::UI);
295
296 WebContentsDeviceUsage* usage = usage_map_.get(web_contents);
297 return usage && usage->IsMirroring();
298 }
299
300 void MediaStreamCaptureIndicator::UnregisterWebContents(
301 WebContents* web_contents) {
302 usage_map_.erase(web_contents);
303 UpdateNotificationUserInterface();
304 }
305
306 void MediaStreamCaptureIndicator::MaybeCreateStatusTrayIcon(bool audio,
307 bool video) {
308 DCHECK_CURRENTLY_ON(BrowserThread::UI);
309
310 if (status_icon_)
311 return;
312
313 // If there is no browser process, we should not create the status tray.
314 if (!g_browser_process)
315 return;
316
317 StatusTray* status_tray = g_browser_process->status_tray();
318 if (!status_tray)
319 return;
320
321 EnsureStatusTrayIconResources();
322
323 gfx::ImageSkia image;
324 base::string16 tool_tip;
325 GetStatusTrayIconInfo(audio, video, &image, &tool_tip);
326 DCHECK(!image.isNull());
327 DCHECK(!tool_tip.empty());
328
329 status_icon_ = status_tray->CreateStatusIcon(
330 StatusTray::MEDIA_STREAM_CAPTURE_ICON, image, tool_tip);
331 }
332
333 void MediaStreamCaptureIndicator::EnsureStatusTrayIconResources() {
334 DCHECK_CURRENTLY_ON(BrowserThread::UI);
335
336 if (!mic_image_) {
337 mic_image_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
338 IDR_INFOBAR_MEDIA_STREAM_MIC);
339 }
340 if (!camera_image_) {
341 camera_image_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
342 IDR_INFOBAR_MEDIA_STREAM_CAMERA);
343 }
344 DCHECK(mic_image_);
345 DCHECK(camera_image_);
346 }
347
348 void MediaStreamCaptureIndicator::MaybeDestroyStatusTrayIcon() {
349 DCHECK_CURRENTLY_ON(BrowserThread::UI);
350
351 if (!status_icon_)
352 return;
353
354 // If there is no browser process, we should not do anything.
355 if (!g_browser_process)
356 return;
357
358 StatusTray* status_tray = g_browser_process->status_tray();
359 if (status_tray != NULL) {
360 status_tray->RemoveStatusIcon(status_icon_);
361 status_icon_ = NULL;
362 }
363 }
364
365 void MediaStreamCaptureIndicator::UpdateNotificationUserInterface() {
366 DCHECK_CURRENTLY_ON(BrowserThread::UI);
367
368 std::unique_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
369 bool audio = false;
370 bool video = false;
371 int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
372 command_targets_.clear();
373
374 for (const auto& it : usage_map_) {
375 // Check if any audio and video devices have been used.
376 const WebContentsDeviceUsage& usage = *it.second;
377 if (!usage.IsCapturingAudio() && !usage.IsCapturingVideo())
378 continue;
379
380 WebContents* const web_contents = it.first;
381
382 // The audio/video icon is shown only for non-whitelisted extensions or on
383 // Android. For regular tabs on desktop, we show an indicator in the tab
384 // icon.
385 #if defined(ENABLE_EXTENSIONS)
386 const extensions::Extension* extension = GetExtension(web_contents);
387 if (!extension || IsWhitelistedExtension(extension))
388 continue;
389 #endif
390
391 audio = audio || usage.IsCapturingAudio();
392 video = video || usage.IsCapturingVideo();
393
394 command_targets_.push_back(web_contents);
395 menu->AddItem(command_id, GetTitle(web_contents));
396
397 // If the menu item is not a label, enable it.
398 menu->SetCommandIdEnabled(command_id, command_id != IDC_MinimumLabelValue);
399
400 // If reaching the maximum number, no more item will be added to the menu.
401 if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST)
402 break;
403 ++command_id;
404 }
405
406 if (command_targets_.empty()) {
407 MaybeDestroyStatusTrayIcon();
408 return;
409 }
410
411 // The icon will take the ownership of the passed context menu.
412 MaybeCreateStatusTrayIcon(audio, video);
413 if (status_icon_) {
414 status_icon_->SetContextMenu(std::move(menu));
415 }
416 }
417
418 void MediaStreamCaptureIndicator::GetStatusTrayIconInfo(
419 bool audio,
420 bool video,
421 gfx::ImageSkia* image,
422 base::string16* tool_tip) {
423 DCHECK_CURRENTLY_ON(BrowserThread::UI);
424 DCHECK(audio || video);
425 DCHECK(image);
426 DCHECK(tool_tip);
427
428 int message_id = 0;
429 if (audio && video) {
430 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_AND_VIDEO;
431 *image = *camera_image_;
432 } else if (audio && !video) {
433 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_ONLY;
434 *image = *mic_image_;
435 } else if (!audio && video) {
436 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_VIDEO_ONLY;
437 *image = *camera_image_;
438 }
439
440 *tool_tip = l10n_util::GetStringUTF16(message_id);
441 }
OLDNEW
« no previous file with comments | « chrome/browser/media/media_stream_capture_indicator.h ('k') | chrome/browser/media/media_stream_device_permission_context.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698