[safe-browsing] Database full hash matches like prefix match.
[chromium-blink-merge.git] / chrome / browser / media / media_stream_capture_indicator.cc
blob24d92921075e3980e394d9b2fcf2378672a8b9c8
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.
5 #include "chrome/browser/media/media_stream_capture_indicator.h"
7 #include "base/bind.h"
8 #include "base/i18n/rtl.h"
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/app/chrome_command_ids.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/status_icons/status_icon.h"
18 #include "chrome/browser/status_icons/status_tray.h"
19 #include "chrome/browser/tab_contents/tab_util.h"
20 #include "chrome/common/extensions/extension_constants.h"
21 #include "chrome/common/pref_names.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/content_browser_client.h"
24 #include "content/public/browser/invalidate_type.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/browser/web_contents_delegate.h"
27 #include "content/public/browser/web_contents_observer.h"
28 #include "extensions/common/extension.h"
29 #include "grit/chromium_strings.h"
30 #include "grit/generated_resources.h"
31 #include "grit/theme_resources.h"
32 #include "net/base/net_util.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/image/image_skia.h"
37 using content::BrowserThread;
38 using content::WebContents;
40 namespace {
42 const extensions::Extension* GetExtension(WebContents* web_contents) {
43 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
45 if (!web_contents)
46 return NULL;
48 Profile* profile =
49 Profile::FromBrowserContext(web_contents->GetBrowserContext());
50 if (!profile)
51 return NULL;
53 ExtensionService* extension_service = profile->GetExtensionService();
54 if (!extension_service)
55 return NULL;
57 return extension_service->extensions()->GetExtensionOrAppByURL(
58 web_contents->GetURL());
61 #if !defined(OS_ANDROID)
63 bool IsWhitelistedExtension(const extensions::Extension* extension) {
64 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
66 static const char* const kExtensionWhitelist[] = {
67 extension_misc::kHotwordExtensionId,
70 for (size_t i = 0; i < arraysize(kExtensionWhitelist); ++i) {
71 if (extension->id() == kExtensionWhitelist[i])
72 return true;
75 return false;
78 #endif // !defined(OS_ANDROID)
80 // Gets the security originator of the tab. It returns a string with no '/'
81 // at the end to display in the UI.
82 base::string16 GetSecurityOrigin(WebContents* web_contents) {
83 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85 if (!web_contents)
86 return base::string16();
88 std::string security_origin = web_contents->GetURL().GetOrigin().spec();
90 // Remove the last character if it is a '/'.
91 if (!security_origin.empty()) {
92 std::string::iterator it = security_origin.end() - 1;
93 if (*it == '/')
94 security_origin.erase(it);
97 return base::UTF8ToUTF16(security_origin);
100 base::string16 GetTitle(WebContents* web_contents) {
101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
103 if (!web_contents)
104 return base::string16();
106 const extensions::Extension* const extension = GetExtension(web_contents);
107 if (extension)
108 return base::UTF8ToUTF16(extension->name());
110 base::string16 tab_title = web_contents->GetTitle();
112 if (tab_title.empty()) {
113 // If the page's title is empty use its security originator.
114 tab_title = GetSecurityOrigin(web_contents);
115 } else {
116 // If the page's title matches its URL, use its security originator.
117 Profile* profile =
118 Profile::FromBrowserContext(web_contents->GetBrowserContext());
119 std::string languages =
120 profile->GetPrefs()->GetString(prefs::kAcceptLanguages);
121 if (tab_title == net::FormatUrl(web_contents->GetURL(), languages))
122 tab_title = GetSecurityOrigin(web_contents);
125 return tab_title;
128 } // namespace
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 the
133 // WebContents instances and delete themselves when corresponding WebContents is
134 // deleted.
135 class MediaStreamCaptureIndicator::WebContentsDeviceUsage
136 : public content::WebContentsObserver {
137 public:
138 explicit WebContentsDeviceUsage(
139 scoped_refptr<MediaStreamCaptureIndicator> indicator,
140 WebContents* web_contents)
141 : WebContentsObserver(web_contents),
142 indicator_(indicator),
143 audio_ref_count_(0),
144 video_ref_count_(0),
145 mirroring_ref_count_(0),
146 weak_factory_(this) {
149 bool IsCapturingAudio() const { return audio_ref_count_ > 0; }
150 bool IsCapturingVideo() const { return video_ref_count_ > 0; }
151 bool IsMirroring() const { return mirroring_ref_count_ > 0; }
153 scoped_ptr<content::MediaStreamUI> RegisterMediaStream(
154 const content::MediaStreamDevices& devices);
156 // Increment ref-counts up based on the type of each device provided.
157 void AddDevices(const content::MediaStreamDevices& devices);
159 // Decrement ref-counts up based on the type of each device provided.
160 void RemoveDevices(const content::MediaStreamDevices& devices);
162 private:
163 // content::WebContentsObserver overrides.
164 virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE {
165 indicator_->UnregisterWebContents(web_contents);
166 delete this;
169 scoped_refptr<MediaStreamCaptureIndicator> indicator_;
170 int audio_ref_count_;
171 int video_ref_count_;
172 int mirroring_ref_count_;
174 base::WeakPtrFactory<WebContentsDeviceUsage> weak_factory_;
176 DISALLOW_COPY_AND_ASSIGN(WebContentsDeviceUsage);
179 // Implements MediaStreamUI interface. Instances of this class are created for
180 // each MediaStream and their ownership is passed to MediaStream implementation
181 // in the content layer. Each UIDelegate keeps a weak pointer to the
182 // corresponding WebContentsDeviceUsage object to deliver updates about state of
183 // the stream.
184 class MediaStreamCaptureIndicator::UIDelegate
185 : public content::MediaStreamUI {
186 public:
187 UIDelegate(base::WeakPtr<WebContentsDeviceUsage> device_usage,
188 const content::MediaStreamDevices& devices)
189 : device_usage_(device_usage),
190 devices_(devices),
191 started_(false) {
192 DCHECK(!devices_.empty());
195 virtual ~UIDelegate() {
196 if (started_ && device_usage_.get())
197 device_usage_->RemoveDevices(devices_);
200 private:
201 // content::MediaStreamUI interface.
202 virtual gfx::NativeViewId OnStarted(const base::Closure& close_callback)
203 OVERRIDE {
204 DCHECK(!started_);
205 started_ = true;
206 if (device_usage_.get())
207 device_usage_->AddDevices(devices_);
208 return 0;
211 base::WeakPtr<WebContentsDeviceUsage> device_usage_;
212 content::MediaStreamDevices devices_;
213 bool started_;
215 DISALLOW_COPY_AND_ASSIGN(UIDelegate);
219 scoped_ptr<content::MediaStreamUI>
220 MediaStreamCaptureIndicator::WebContentsDeviceUsage::RegisterMediaStream(
221 const content::MediaStreamDevices& devices) {
222 return scoped_ptr<content::MediaStreamUI>(new UIDelegate(
223 weak_factory_.GetWeakPtr(), devices));
226 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::AddDevices(
227 const content::MediaStreamDevices& devices) {
228 for (content::MediaStreamDevices::const_iterator it = devices.begin();
229 it != devices.end(); ++it) {
230 if (it->type == content::MEDIA_TAB_AUDIO_CAPTURE ||
231 it->type == content::MEDIA_TAB_VIDEO_CAPTURE) {
232 ++mirroring_ref_count_;
233 } else if (content::IsAudioMediaType(it->type)) {
234 ++audio_ref_count_;
235 } else if (content::IsVideoMediaType(it->type)) {
236 ++video_ref_count_;
237 } else {
238 NOTIMPLEMENTED();
242 if (web_contents())
243 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
245 indicator_->UpdateNotificationUserInterface();
248 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::RemoveDevices(
249 const content::MediaStreamDevices& devices) {
250 for (content::MediaStreamDevices::const_iterator it = devices.begin();
251 it != devices.end(); ++it) {
252 if (it->type == content::MEDIA_TAB_AUDIO_CAPTURE ||
253 it->type == content::MEDIA_TAB_VIDEO_CAPTURE) {
254 --mirroring_ref_count_;
255 } else if (content::IsAudioMediaType(it->type)) {
256 --audio_ref_count_;
257 } else if (content::IsVideoMediaType(it->type)) {
258 --video_ref_count_;
259 } else {
260 NOTIMPLEMENTED();
264 DCHECK_GE(audio_ref_count_, 0);
265 DCHECK_GE(video_ref_count_, 0);
266 DCHECK_GE(mirroring_ref_count_, 0);
268 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
269 indicator_->UpdateNotificationUserInterface();
272 MediaStreamCaptureIndicator::MediaStreamCaptureIndicator()
273 : status_icon_(NULL),
274 mic_image_(NULL),
275 camera_image_(NULL) {
278 MediaStreamCaptureIndicator::~MediaStreamCaptureIndicator() {
279 // The user is responsible for cleaning up by reporting the closure of any
280 // opened devices. However, there exists a race condition at shutdown: The UI
281 // thread may be stopped before CaptureDevicesClosed() posts the task to
282 // invoke DoDevicesClosedOnUIThread(). In this case, usage_map_ won't be
283 // empty like it should.
284 DCHECK(usage_map_.empty() ||
285 !BrowserThread::IsMessageLoopValid(BrowserThread::UI));
287 // Free any WebContentsDeviceUsage objects left over.
288 for (UsageMap::const_iterator it = usage_map_.begin(); it != usage_map_.end();
289 ++it) {
290 delete it->second;
294 scoped_ptr<content::MediaStreamUI>
295 MediaStreamCaptureIndicator::RegisterMediaStream(
296 content::WebContents* web_contents,
297 const content::MediaStreamDevices& devices) {
298 WebContentsDeviceUsage*& usage = usage_map_[web_contents];
299 if (!usage)
300 usage = new WebContentsDeviceUsage(this, web_contents);
301 return usage->RegisterMediaStream(devices);
304 void MediaStreamCaptureIndicator::ExecuteCommand(int command_id,
305 int event_flags) {
306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308 const int index =
309 command_id - IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
310 DCHECK_LE(0, index);
311 DCHECK_GT(static_cast<int>(command_targets_.size()), index);
312 WebContents* const web_contents = command_targets_[index];
313 UsageMap::const_iterator it = usage_map_.find(web_contents);
314 if (it == usage_map_.end())
315 return;
316 web_contents->GetDelegate()->ActivateContents(web_contents);
319 bool MediaStreamCaptureIndicator::IsCapturingUserMedia(
320 content::WebContents* web_contents) const {
321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
323 UsageMap::const_iterator it = usage_map_.find(web_contents);
324 return (it != usage_map_.end() &&
325 (it->second->IsCapturingAudio() || it->second->IsCapturingVideo()));
328 bool MediaStreamCaptureIndicator::IsCapturingVideo(
329 content::WebContents* web_contents) const {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
332 UsageMap::const_iterator it = usage_map_.find(web_contents);
333 return (it != usage_map_.end() && it->second->IsCapturingVideo());
336 bool MediaStreamCaptureIndicator::IsCapturingAudio(
337 content::WebContents* web_contents) const {
338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
340 UsageMap::const_iterator it = usage_map_.find(web_contents);
341 return (it != usage_map_.end() && it->second->IsCapturingAudio());
344 bool MediaStreamCaptureIndicator::IsBeingMirrored(
345 content::WebContents* web_contents) const {
346 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
348 UsageMap::const_iterator it = usage_map_.find(web_contents);
349 return it != usage_map_.end() && it->second->IsMirroring();
352 void MediaStreamCaptureIndicator::UnregisterWebContents(
353 WebContents* web_contents) {
354 usage_map_.erase(web_contents);
355 UpdateNotificationUserInterface();
358 void MediaStreamCaptureIndicator::MaybeCreateStatusTrayIcon(bool audio,
359 bool video) {
360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
361 if (status_icon_)
362 return;
364 // If there is no browser process, we should not create the status tray.
365 if (!g_browser_process)
366 return;
368 StatusTray* status_tray = g_browser_process->status_tray();
369 if (!status_tray)
370 return;
372 EnsureStatusTrayIconResources();
374 gfx::ImageSkia image;
375 base::string16 tool_tip;
376 GetStatusTrayIconInfo(audio, video, &image, &tool_tip);
377 DCHECK(!image.isNull());
378 DCHECK(!tool_tip.empty());
380 status_icon_ = status_tray->CreateStatusIcon(
381 StatusTray::MEDIA_STREAM_CAPTURE_ICON, image, tool_tip);
384 void MediaStreamCaptureIndicator::EnsureStatusTrayIconResources() {
385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
386 if (!mic_image_) {
387 mic_image_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
388 IDR_INFOBAR_MEDIA_STREAM_MIC);
390 if (!camera_image_) {
391 camera_image_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
392 IDR_INFOBAR_MEDIA_STREAM_CAMERA);
394 DCHECK(mic_image_);
395 DCHECK(camera_image_);
398 void MediaStreamCaptureIndicator::MaybeDestroyStatusTrayIcon() {
399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
401 if (!status_icon_)
402 return;
404 // If there is no browser process, we should not do anything.
405 if (!g_browser_process)
406 return;
408 StatusTray* status_tray = g_browser_process->status_tray();
409 if (status_tray != NULL) {
410 status_tray->RemoveStatusIcon(status_icon_);
411 status_icon_ = NULL;
415 void MediaStreamCaptureIndicator::UpdateNotificationUserInterface() {
416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
417 scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
419 bool audio = false;
420 bool video = false;
421 int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
422 command_targets_.clear();
424 for (UsageMap::const_iterator iter = usage_map_.begin();
425 iter != usage_map_.end(); ++iter) {
426 // Check if any audio and video devices have been used.
427 const WebContentsDeviceUsage& usage = *iter->second;
428 if (!usage.IsCapturingAudio() && !usage.IsCapturingVideo())
429 continue;
431 WebContents* const web_contents = iter->first;
433 // The audio/video icon is shown only for non-whitelisted extensions or on
434 // Android. For regular tabs on desktop, we show an indicator in the tab
435 // icon.
436 #if !defined(OS_ANDROID)
437 const extensions::Extension* extension = GetExtension(web_contents);
438 if (!extension || IsWhitelistedExtension(extension))
439 continue;
440 #endif
442 audio = audio || usage.IsCapturingAudio();
443 video = video || usage.IsCapturingVideo();
445 command_targets_.push_back(web_contents);
446 menu->AddItem(command_id, GetTitle(web_contents));
448 // If the menu item is not a label, enable it.
449 menu->SetCommandIdEnabled(command_id,
450 command_id != IDC_MinimumLabelValue);
452 // If reaching the maximum number, no more item will be added to the menu.
453 if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST)
454 break;
455 ++command_id;
458 if (command_targets_.empty()) {
459 MaybeDestroyStatusTrayIcon();
460 return;
463 // The icon will take the ownership of the passed context menu.
464 MaybeCreateStatusTrayIcon(audio, video);
465 if (status_icon_) {
466 status_icon_->SetContextMenu(menu.Pass());
470 void MediaStreamCaptureIndicator::GetStatusTrayIconInfo(
471 bool audio,
472 bool video,
473 gfx::ImageSkia* image,
474 base::string16* tool_tip) {
475 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
476 DCHECK(audio || video);
478 int message_id = 0;
479 if (audio && video) {
480 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_AND_VIDEO;
481 *image = *camera_image_;
482 } else if (audio && !video) {
483 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_ONLY;
484 *image = *mic_image_;
485 } else if (!audio && video) {
486 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_VIDEO_ONLY;
487 *image = *camera_image_;
490 *tool_tip = l10n_util::GetStringUTF16(message_id);