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/webrtc/media_stream_capture_indicator.h"
6 
7 #include <stddef.h>
8 
9 #include <memory>
10 #include <string>
11 #include <utility>
12 
13 #include "base/check_op.h"
14 #include "base/macros.h"
15 #include "base/notreached.h"
16 #include "build/build_config.h"
17 #include "chrome/app/chrome_command_ids.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/content_settings/chrome_content_settings_utils.h"
20 #include "chrome/browser/status_icons/status_icon.h"
21 #include "chrome/browser/status_icons/status_tray.h"
22 #include "chrome/browser/tab_contents/tab_util.h"
23 #include "components/url_formatter/elide_url.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/content_browser_client.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/browser/web_contents_delegate.h"
28 #include "content/public/browser/web_contents_observer.h"
29 #include "extensions/buildflags/buildflags.h"
30 #include "ui/gfx/image/image_skia.h"
31 
32 #if !defined(OS_ANDROID)
33 #include "chrome/grit/chromium_strings.h"
34 #include "components/vector_icons/vector_icons.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/gfx/color_palette.h"
37 #include "ui/gfx/paint_vector_icon.h"
38 #endif
39 
40 #if BUILDFLAG(ENABLE_EXTENSIONS)
41 #include "base/strings/utf_string_conversions.h"
42 #include "chrome/common/extensions/extension_constants.h"
43 #include "extensions/browser/extension_registry.h"
44 #include "extensions/common/extension.h"
45 #endif
46 
47 #if defined(OS_CHROMEOS)
48 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
49 #endif
50 
51 using content::BrowserThread;
52 using content::WebContents;
53 
54 namespace {
55 
56 #if BUILDFLAG(ENABLE_EXTENSIONS)
GetExtension(WebContents * web_contents)57 const extensions::Extension* GetExtension(WebContents* web_contents) {
58   DCHECK_CURRENTLY_ON(BrowserThread::UI);
59 
60   if (!web_contents)
61     return nullptr;
62 
63   extensions::ExtensionRegistry* registry =
64       extensions::ExtensionRegistry::Get(web_contents->GetBrowserContext());
65   return registry->enabled_extensions().GetExtensionOrAppByURL(
66       web_contents->GetURL());
67 }
68 
69 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
70 
GetTitle(WebContents * web_contents)71 base::string16 GetTitle(WebContents* web_contents) {
72   DCHECK_CURRENTLY_ON(BrowserThread::UI);
73 
74   if (!web_contents)
75     return base::string16();
76 
77 #if BUILDFLAG(ENABLE_EXTENSIONS)
78   const extensions::Extension* const extension = GetExtension(web_contents);
79   if (extension)
80     return base::UTF8ToUTF16(extension->name());
81 #endif
82 
83   return url_formatter::FormatUrlForSecurityDisplay(web_contents->GetURL());
84 }
85 
86 // Returns if the passed |device| is capturing the whole display. This is
87 // different from capturing a tab or a single window on a desktop.
IsDeviceCapturingDisplay(const blink::MediaStreamDevice & device)88 bool IsDeviceCapturingDisplay(const blink::MediaStreamDevice& device) {
89   return device.display_media_info &&
90          device.display_media_info.value()->display_surface ==
91              media::mojom::DisplayCaptureSurfaceType::MONITOR;
92 }
93 
94 typedef void (MediaStreamCaptureIndicator::Observer::*ObserverMethod)(
95     content::WebContents* web_contents,
96     bool value);
97 
GetObserverMethodToCall(const blink::MediaStreamDevice & device)98 ObserverMethod GetObserverMethodToCall(const blink::MediaStreamDevice& device) {
99   switch (device.type) {
100     case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
101       return &MediaStreamCaptureIndicator::Observer::OnIsCapturingAudioChanged;
102 
103     case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
104       return &MediaStreamCaptureIndicator::Observer::OnIsCapturingVideoChanged;
105 
106     case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE:
107     case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE:
108       return &MediaStreamCaptureIndicator::Observer::OnIsBeingMirroredChanged;
109 
110     case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE:
111     case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE:
112     case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE:
113     case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE:
114     case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB:
115       return IsDeviceCapturingDisplay(device)
116                  ? &MediaStreamCaptureIndicator::Observer::
117                        OnIsCapturingDisplayChanged
118                  : &MediaStreamCaptureIndicator::Observer::
119                        OnIsCapturingWindowChanged;
120 
121     case blink::mojom::MediaStreamType::NO_SERVICE:
122     case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES:
123       NOTREACHED();
124       return nullptr;
125   }
126 }
127 
128 }  // namespace
129 
130 // Stores usage counts for all the capture devices associated with a single
131 // WebContents instance. Instances of this class are owned by
132 // MediaStreamCaptureIndicator. They also observe for the destruction of their
133 // corresponding WebContents and trigger their own deletion from their
134 // MediaStreamCaptureIndicator.
135 class MediaStreamCaptureIndicator::WebContentsDeviceUsage
136     : public content::WebContentsObserver {
137  public:
WebContentsDeviceUsage(scoped_refptr<MediaStreamCaptureIndicator> indicator,WebContents * web_contents)138   WebContentsDeviceUsage(scoped_refptr<MediaStreamCaptureIndicator> indicator,
139                          WebContents* web_contents)
140       : WebContentsObserver(web_contents), indicator_(std::move(indicator)) {}
141 
IsCapturingAudio() const142   bool IsCapturingAudio() const { return audio_stream_count_ > 0; }
IsCapturingVideo() const143   bool IsCapturingVideo() const { return video_stream_count_ > 0; }
IsMirroring() const144   bool IsMirroring() const { return mirroring_stream_count_ > 0; }
IsCapturingWindow() const145   bool IsCapturingWindow() const { return window_stream_count_ > 0; }
IsCapturingDisplay() const146   bool IsCapturingDisplay() const { return display_stream_count_ > 0; }
147 
148   std::unique_ptr<content::MediaStreamUI> RegisterMediaStream(
149       const blink::MediaStreamDevices& devices,
150       std::unique_ptr<MediaStreamUI> ui);
151 
152   // Increment ref-counts up based on the type of each device provided.
153   void AddDevices(const blink::MediaStreamDevices& devices,
154                   base::OnceClosure stop_callback);
155 
156   // Decrement ref-counts up based on the type of each device provided.
157   void RemoveDevices(const blink::MediaStreamDevices& devices);
158 
159   // Helper to call |stop_callback_|.
160   void NotifyStopped();
161 
162  private:
163   int& GetStreamCount(const blink::MediaStreamDevice& device);
164 
165   // content::WebContentsObserver overrides.
WebContentsDestroyed()166   void WebContentsDestroyed() override {
167     indicator_->UnregisterWebContents(web_contents());
168   }
169 
170   scoped_refptr<MediaStreamCaptureIndicator> indicator_;
171   int audio_stream_count_ = 0;
172   int video_stream_count_ = 0;
173   int mirroring_stream_count_ = 0;
174   int window_stream_count_ = 0;
175   int display_stream_count_ = 0;
176 
177   base::OnceClosure stop_callback_;
178   base::WeakPtrFactory<WebContentsDeviceUsage> weak_factory_{this};
179 
180   DISALLOW_COPY_AND_ASSIGN(WebContentsDeviceUsage);
181 };
182 
183 // Implements MediaStreamUI interface. Instances of this class are created for
184 // each MediaStream and their ownership is passed to MediaStream implementation
185 // in the content layer. Each UIDelegate keeps a weak pointer to the
186 // corresponding WebContentsDeviceUsage object to deliver updates about state of
187 // the stream.
188 class MediaStreamCaptureIndicator::UIDelegate : public content::MediaStreamUI {
189  public:
UIDelegate(base::WeakPtr<WebContentsDeviceUsage> device_usage,const blink::MediaStreamDevices & devices,std::unique_ptr<::MediaStreamUI> ui)190   UIDelegate(base::WeakPtr<WebContentsDeviceUsage> device_usage,
191              const blink::MediaStreamDevices& devices,
192              std::unique_ptr<::MediaStreamUI> ui)
193       : device_usage_(device_usage), devices_(devices), ui_(std::move(ui)) {
194     DCHECK(!devices_.empty());
195   }
196 
~UIDelegate()197   ~UIDelegate() override {
198     if (started_ && device_usage_)
199       device_usage_->RemoveDevices(devices_);
200   }
201 
202  private:
203   // content::MediaStreamUI interface.
OnStarted(base::OnceClosure stop_callback,content::MediaStreamUI::SourceCallback source_callback,const std::string & label,std::vector<content::DesktopMediaID> screen_capture_ids,StateChangeCallback state_change_callback)204   gfx::NativeViewId OnStarted(
205       base::OnceClosure stop_callback,
206       content::MediaStreamUI::SourceCallback source_callback,
207       const std::string& label,
208       std::vector<content::DesktopMediaID> screen_capture_ids,
209       StateChangeCallback state_change_callback) override {
210     if (started_) {
211       // Ignore possibly-compromised renderers that might call
212       // MediaStreamDispatcherHost::OnStreamStarted() more than once.
213       // See: https://crbug.com/1155426
214       return 0;
215     }
216     started_ = true;
217 
218     if (device_usage_) {
219       // |device_usage_| handles |stop_callback| when |ui_| is unspecified.
220       device_usage_->AddDevices(
221           devices_, ui_ ? base::OnceClosure() : std::move(stop_callback));
222     }
223 
224 #if defined(OS_CHROMEOS)
225     policy::DlpContentManager::Get()->OnScreenCaptureStarted(
226         label, screen_capture_ids, state_change_callback);
227 #endif
228 
229     // If a custom |ui_| is specified, notify it that the stream started and let
230     // it handle the |stop_callback| and |source_callback|.
231     if (ui_)
232       return ui_->OnStarted(std::move(stop_callback),
233                             std::move(source_callback));
234 
235     return 0;
236   }
237 
OnDeviceStopped(const std::string & label,const content::DesktopMediaID & media_id)238   void OnDeviceStopped(const std::string& label,
239                        const content::DesktopMediaID& media_id) override {
240 #if defined(OS_CHROMEOS)
241     policy::DlpContentManager::Get()->OnScreenCaptureStopped(label, media_id);
242 #endif
243   }
244 
SetStopCallback(base::OnceClosure stop_callback)245   void SetStopCallback(base::OnceClosure stop_callback) override {
246     if (ui_) {
247       ui_->SetStopCallback(std::move(stop_callback));
248     }
249   }
250 
251   base::WeakPtr<WebContentsDeviceUsage> device_usage_;
252   const blink::MediaStreamDevices devices_;
253   const std::unique_ptr<::MediaStreamUI> ui_;
254   bool started_ = false;
255 
256   DISALLOW_COPY_AND_ASSIGN(UIDelegate);
257 };
258 
259 std::unique_ptr<content::MediaStreamUI>
RegisterMediaStream(const blink::MediaStreamDevices & devices,std::unique_ptr<MediaStreamUI> ui)260 MediaStreamCaptureIndicator::WebContentsDeviceUsage::RegisterMediaStream(
261     const blink::MediaStreamDevices& devices,
262     std::unique_ptr<MediaStreamUI> ui) {
263   return std::make_unique<UIDelegate>(weak_factory_.GetWeakPtr(), devices,
264                                       std::move(ui));
265 }
266 
AddDevices(const blink::MediaStreamDevices & devices,base::OnceClosure stop_callback)267 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::AddDevices(
268     const blink::MediaStreamDevices& devices,
269     base::OnceClosure stop_callback) {
270   for (const auto& device : devices) {
271     int& stream_count = GetStreamCount(device);
272     ++stream_count;
273 
274     if (web_contents() && stream_count == 1) {
275       ObserverMethod obs_func = GetObserverMethodToCall(device);
276       DCHECK(obs_func);
277       for (Observer& obs : indicator_->observers_)
278         (obs.*obs_func)(web_contents(), true);
279     }
280   }
281 
282   if (web_contents()) {
283     stop_callback_ = std::move(stop_callback);
284     web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
285   }
286 
287   indicator_->UpdateNotificationUserInterface();
288 }
289 
RemoveDevices(const blink::MediaStreamDevices & devices)290 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::RemoveDevices(
291     const blink::MediaStreamDevices& devices) {
292   for (const auto& device : devices) {
293     int& stream_count = GetStreamCount(device);
294     --stream_count;
295     DCHECK_GE(stream_count, 0);
296 
297     if (web_contents() && stream_count == 0) {
298       ObserverMethod obs_func = GetObserverMethodToCall(device);
299       DCHECK(obs_func);
300       for (Observer& obs : indicator_->observers_)
301         (obs.*obs_func)(web_contents(), false);
302     }
303   }
304 
305   if (web_contents()) {
306     web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
307     content_settings::UpdateLocationBarUiForWebContents(web_contents());
308   }
309 
310   indicator_->UpdateNotificationUserInterface();
311 }
312 
NotifyStopped()313 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::NotifyStopped() {
314   if (stop_callback_)
315     std::move(stop_callback_).Run();
316 }
317 
GetStreamCount(const blink::MediaStreamDevice & device)318 int& MediaStreamCaptureIndicator::WebContentsDeviceUsage::GetStreamCount(
319     const blink::MediaStreamDevice& device) {
320   switch (device.type) {
321     case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
322       return audio_stream_count_;
323 
324     case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
325       return video_stream_count_;
326 
327     case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE:
328     case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE:
329       return mirroring_stream_count_;
330 
331     case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE:
332     case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE:
333     case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE:
334     case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE:
335     case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB:
336       return IsDeviceCapturingDisplay(device) ? display_stream_count_
337                                               : window_stream_count_;
338 
339     case blink::mojom::MediaStreamType::NO_SERVICE:
340     case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES:
341       NOTREACHED();
342       return video_stream_count_;
343   }
344 }
345 
~Observer()346 MediaStreamCaptureIndicator::Observer::~Observer() {
347   DCHECK(!IsInObserverList());
348 }
349 
MediaStreamCaptureIndicator()350 MediaStreamCaptureIndicator::MediaStreamCaptureIndicator() {}
351 
~MediaStreamCaptureIndicator()352 MediaStreamCaptureIndicator::~MediaStreamCaptureIndicator() {
353   // The user is responsible for cleaning up by reporting the closure of any
354   // opened devices.  However, there exists a race condition at shutdown: The UI
355   // thread may be stopped before CaptureDevicesClosed() posts the task to
356   // invoke DoDevicesClosedOnUIThread().  In this case, usage_map_ won't be
357   // empty like it should.
358   DCHECK(usage_map_.empty() ||
359          !BrowserThread::IsThreadInitialized(BrowserThread::UI));
360 }
361 
362 std::unique_ptr<content::MediaStreamUI>
RegisterMediaStream(content::WebContents * web_contents,const blink::MediaStreamDevices & devices,std::unique_ptr<MediaStreamUI> ui)363 MediaStreamCaptureIndicator::RegisterMediaStream(
364     content::WebContents* web_contents,
365     const blink::MediaStreamDevices& devices,
366     std::unique_ptr<MediaStreamUI> ui) {
367   DCHECK(web_contents);
368   auto& usage = usage_map_[web_contents];
369   if (!usage)
370     usage = std::make_unique<WebContentsDeviceUsage>(this, web_contents);
371 
372   return usage->RegisterMediaStream(devices, std::move(ui));
373 }
374 
ExecuteCommand(int command_id,int event_flags)375 void MediaStreamCaptureIndicator::ExecuteCommand(int command_id,
376                                                  int event_flags) {
377   DCHECK_CURRENTLY_ON(BrowserThread::UI);
378 
379   const int index =
380       command_id - IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
381   DCHECK_LE(0, index);
382   DCHECK_GT(static_cast<int>(command_targets_.size()), index);
383   WebContents* web_contents = command_targets_[index];
384   if (base::Contains(usage_map_, web_contents))
385     web_contents->GetDelegate()->ActivateContents(web_contents);
386 }
387 
CheckUsage(content::WebContents * web_contents,const WebContentsDeviceUsagePredicate & pred) const388 bool MediaStreamCaptureIndicator::CheckUsage(
389     content::WebContents* web_contents,
390     const WebContentsDeviceUsagePredicate& pred) const {
391   auto it = usage_map_.find(web_contents);
392   if (it != usage_map_.end() && pred.Run(it->second.get()))
393     return true;
394 
395   for (auto* inner_contents : web_contents->GetInnerWebContents()) {
396     if (CheckUsage(inner_contents, pred))
397       return true;
398   }
399 
400   return false;
401 }
402 
IsCapturingUserMedia(content::WebContents * web_contents) const403 bool MediaStreamCaptureIndicator::IsCapturingUserMedia(
404     content::WebContents* web_contents) const {
405   DCHECK_CURRENTLY_ON(BrowserThread::UI);
406   return CheckUsage(
407       web_contents,
408       base::BindRepeating([](const WebContentsDeviceUsage* usage) {
409         return usage->IsCapturingAudio() || usage->IsCapturingVideo();
410       }));
411 }
412 
IsCapturingVideo(content::WebContents * web_contents) const413 bool MediaStreamCaptureIndicator::IsCapturingVideo(
414     content::WebContents* web_contents) const {
415   DCHECK_CURRENTLY_ON(BrowserThread::UI);
416   return CheckUsage(
417       web_contents,
418       base::BindRepeating(&WebContentsDeviceUsage::IsCapturingVideo));
419 }
420 
IsCapturingAudio(content::WebContents * web_contents) const421 bool MediaStreamCaptureIndicator::IsCapturingAudio(
422     content::WebContents* web_contents) const {
423   DCHECK_CURRENTLY_ON(BrowserThread::UI);
424   return CheckUsage(
425       web_contents,
426       base::BindRepeating(&WebContentsDeviceUsage::IsCapturingAudio));
427 }
428 
IsBeingMirrored(content::WebContents * web_contents) const429 bool MediaStreamCaptureIndicator::IsBeingMirrored(
430     content::WebContents* web_contents) const {
431   DCHECK_CURRENTLY_ON(BrowserThread::UI);
432   return CheckUsage(web_contents,
433                     base::BindRepeating(&WebContentsDeviceUsage::IsMirroring));
434 }
435 
IsCapturingWindow(content::WebContents * web_contents) const436 bool MediaStreamCaptureIndicator::IsCapturingWindow(
437     content::WebContents* web_contents) const {
438   DCHECK_CURRENTLY_ON(BrowserThread::UI);
439   return CheckUsage(
440       web_contents,
441       base::BindRepeating(&WebContentsDeviceUsage::IsCapturingWindow));
442 }
443 
IsCapturingDisplay(content::WebContents * web_contents) const444 bool MediaStreamCaptureIndicator::IsCapturingDisplay(
445     content::WebContents* web_contents) const {
446   DCHECK_CURRENTLY_ON(BrowserThread::UI);
447   return CheckUsage(
448       web_contents,
449       base::BindRepeating(&WebContentsDeviceUsage::IsCapturingDisplay));
450 }
451 
NotifyStopped(content::WebContents * web_contents) const452 void MediaStreamCaptureIndicator::NotifyStopped(
453     content::WebContents* web_contents) const {
454   DCHECK_CURRENTLY_ON(BrowserThread::UI);
455 
456   auto it = usage_map_.find(web_contents);
457   DCHECK(it != usage_map_.end());
458   it->second->NotifyStopped();
459 
460   for (auto* inner_contents : web_contents->GetInnerWebContents())
461     NotifyStopped(inner_contents);
462 }
463 
UnregisterWebContents(WebContents * web_contents)464 void MediaStreamCaptureIndicator::UnregisterWebContents(
465     WebContents* web_contents) {
466   if (IsCapturingVideo(web_contents)) {
467     for (Observer& observer : observers_)
468       observer.OnIsCapturingVideoChanged(web_contents, false);
469   }
470   if (IsCapturingAudio(web_contents)) {
471     for (Observer& observer : observers_)
472       observer.OnIsCapturingAudioChanged(web_contents, false);
473   }
474   if (IsBeingMirrored(web_contents)) {
475     for (Observer& observer : observers_)
476       observer.OnIsBeingMirroredChanged(web_contents, false);
477   }
478   if (IsCapturingWindow(web_contents)) {
479     for (Observer& observer : observers_)
480       observer.OnIsCapturingWindowChanged(web_contents, false);
481   }
482   if (IsCapturingDisplay(web_contents)) {
483     for (Observer& observer : observers_)
484       observer.OnIsCapturingDisplayChanged(web_contents, false);
485   }
486   usage_map_.erase(web_contents);
487   UpdateNotificationUserInterface();
488 }
489 
MaybeCreateStatusTrayIcon(bool audio,bool video)490 void MediaStreamCaptureIndicator::MaybeCreateStatusTrayIcon(bool audio,
491                                                             bool video) {
492   DCHECK_CURRENTLY_ON(BrowserThread::UI);
493 
494   if (status_icon_)
495     return;
496 
497   // If there is no browser process, we should not create the status tray.
498   if (!g_browser_process)
499     return;
500 
501   StatusTray* status_tray = g_browser_process->status_tray();
502   if (!status_tray)
503     return;
504 
505   gfx::ImageSkia image;
506   base::string16 tool_tip;
507   GetStatusTrayIconInfo(audio, video, &image, &tool_tip);
508   DCHECK(!image.isNull());
509   DCHECK(!tool_tip.empty());
510 
511   status_icon_ = status_tray->CreateStatusIcon(
512       StatusTray::MEDIA_STREAM_CAPTURE_ICON, image, tool_tip);
513 }
514 
MaybeDestroyStatusTrayIcon()515 void MediaStreamCaptureIndicator::MaybeDestroyStatusTrayIcon() {
516   DCHECK_CURRENTLY_ON(BrowserThread::UI);
517 
518   if (!status_icon_)
519     return;
520 
521   // If there is no browser process, we should not do anything.
522   if (!g_browser_process)
523     return;
524 
525   StatusTray* status_tray = g_browser_process->status_tray();
526   if (status_tray != nullptr) {
527     status_tray->RemoveStatusIcon(status_icon_);
528     status_icon_ = nullptr;
529   }
530 }
531 
UpdateNotificationUserInterface()532 void MediaStreamCaptureIndicator::UpdateNotificationUserInterface() {
533   DCHECK_CURRENTLY_ON(BrowserThread::UI);
534 
535   std::unique_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
536   bool audio = false;
537   bool video = false;
538   int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
539   command_targets_.clear();
540 
541   for (const auto& it : usage_map_) {
542     // Check if any audio and video devices have been used.
543     const WebContentsDeviceUsage& usage = *it.second;
544     if (!usage.IsCapturingAudio() && !usage.IsCapturingVideo())
545       continue;
546 
547     WebContents* const web_contents = it.first;
548 
549     // The audio/video icon is shown for extensions or on Android. For
550     // regular tabs on desktop, we show an indicator in the tab icon.
551 #if BUILDFLAG(ENABLE_EXTENSIONS)
552     const extensions::Extension* extension = GetExtension(web_contents);
553     if (!extension)
554       continue;
555 #endif
556 
557     audio = audio || usage.IsCapturingAudio();
558     video = video || usage.IsCapturingVideo();
559 
560     command_targets_.push_back(web_contents);
561     menu->AddItem(command_id, GetTitle(web_contents));
562 
563     // If the menu item is not a label, enable it.
564     menu->SetCommandIdEnabled(command_id, command_id != IDC_MinimumLabelValue);
565 
566     // If reaching the maximum number, no more item will be added to the menu.
567     if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST)
568       break;
569     ++command_id;
570   }
571 
572   if (command_targets_.empty()) {
573     MaybeDestroyStatusTrayIcon();
574     return;
575   }
576 
577   // The icon will take the ownership of the passed context menu.
578   MaybeCreateStatusTrayIcon(audio, video);
579   if (status_icon_) {
580     status_icon_->SetContextMenu(std::move(menu));
581   }
582 }
583 
GetStatusTrayIconInfo(bool audio,bool video,gfx::ImageSkia * image,base::string16 * tool_tip)584 void MediaStreamCaptureIndicator::GetStatusTrayIconInfo(
585     bool audio,
586     bool video,
587     gfx::ImageSkia* image,
588     base::string16* tool_tip) {
589 #if defined(OS_ANDROID)
590   NOTREACHED();
591 #else   // !defined(OS_ANDROID)
592   DCHECK_CURRENTLY_ON(BrowserThread::UI);
593   DCHECK(audio || video);
594   DCHECK(image);
595   DCHECK(tool_tip);
596 
597   int message_id = 0;
598   const gfx::VectorIcon* icon = nullptr;
599   if (audio && video) {
600     message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_AND_VIDEO;
601     icon = &vector_icons::kVideocamIcon;
602   } else if (audio && !video) {
603     message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_ONLY;
604     icon = &vector_icons::kMicIcon;
605   } else if (!audio && video) {
606     message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_VIDEO_ONLY;
607     icon = &vector_icons::kVideocamIcon;
608   }
609 
610   *tool_tip = l10n_util::GetStringUTF16(message_id);
611   *image = gfx::CreateVectorIcon(*icon, 16, gfx::kChromeIconGrey);
612 #endif  // !defined(OS_ANDROID)
613 }
614