| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/notifications/screen_capture_notification_blocker.h" |
| |
| #include <algorithm> |
| |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" |
| #include "chrome/browser/notifications/notification_display_service.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "content/public/browser/web_contents.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace { |
| |
| void RecordScreenCaptureCount(const std::string& suffix, int count) { |
| base::UmaHistogramCounts100( |
| base::StrCat({"Notifications.Blocker.ScreenCapture.", suffix}), count); |
| } |
| |
| } // namespace |
| |
| const char kMuteNotificationId[] = "notifications_muted"; |
| |
| ScreenCaptureNotificationBlocker::ScreenCaptureNotificationBlocker( |
| NotificationDisplayService* notification_display_service) |
| : notification_display_service_(notification_display_service) { |
| DCHECK(notification_display_service_); |
| observation_.Observe(MediaCaptureDevicesDispatcher::GetInstance() |
| ->GetMediaStreamCaptureIndicator() |
| .get()); |
| } |
| |
| ScreenCaptureNotificationBlocker::~ScreenCaptureNotificationBlocker() = default; |
| |
| bool ScreenCaptureNotificationBlocker::ShouldBlockNotification( |
| const message_center::Notification& notification) { |
| // Don't block if the user clicked on "Show" for the current session. |
| if (state_ == NotifyState::kShowAll) |
| return false; |
| |
| // Don't block if no WebContents currently captures the screen. |
| if (capturing_web_contents_.empty()) |
| return false; |
| |
| // Otherwise block all notifications that belong to non-capturing origins. |
| return std::ranges::none_of( |
| capturing_web_contents_, |
| [¬ification](content::WebContents* web_contents) { |
| return url::IsSameOriginWith(notification.origin_url(), |
| web_contents->GetLastCommittedURL()); |
| }); |
| } |
| |
| void ScreenCaptureNotificationBlocker::OnBlockedNotification( |
| const message_center::Notification& notification, |
| bool replaced) { |
| if (replaced) |
| ++replaced_notification_count_; |
| else |
| ++muted_notification_count_; |
| |
| if (state_ == NotifyState::kSnooze) |
| ++snoozed_notification_count_; |
| |
| if (state_ == NotifyState::kNotifyMuted) |
| DisplayMuteNotification(); |
| } |
| |
| void ScreenCaptureNotificationBlocker::OnClosedNotification( |
| const message_center::Notification& notification) { |
| ++closed_notification_count_; |
| } |
| |
| void ScreenCaptureNotificationBlocker::OnAction( |
| MutedNotificationHandler::Action action) { |
| DCHECK(state_ == NotifyState::kNotifyMuted); |
| CloseMuteNotification(); |
| |
| switch (action) { |
| case MutedNotificationHandler::Action::kUserClose: |
| case MutedNotificationHandler::Action::kBodyClick: |
| // Nothing to do here. |
| break; |
| case MutedNotificationHandler::Action::kShowClick: |
| state_ = NotifyState::kShowAll; |
| NotifyBlockingStateChanged(); |
| ReportSessionMetrics(/*revealed=*/true); |
| break; |
| case MutedNotificationHandler::Action::kSnoozeClick: |
| state_ = NotifyState::kSnooze; |
| break; |
| } |
| } |
| |
| void ScreenCaptureNotificationBlocker::OnIsCapturingDisplayChanged( |
| content::WebContents* web_contents, |
| bool is_capturing_display) { |
| if (is_capturing_display) |
| capturing_web_contents_.insert(web_contents); |
| else |
| capturing_web_contents_.erase(web_contents); |
| |
| if (capturing_web_contents_.empty()) { |
| ReportSessionMetrics(/*revealed=*/false); |
| muted_notification_count_ = 0; |
| replaced_notification_count_ = 0; |
| closed_notification_count_ = 0; |
| snoozed_notification_count_ = 0; |
| reported_session_metrics_ = false; |
| state_ = NotifyState::kNotifyMuted; |
| last_screen_capture_session_start_time_ = base::TimeTicks(); |
| CloseMuteNotification(); |
| } else if (last_screen_capture_session_start_time_.is_null()) { |
| last_screen_capture_session_start_time_ = base::TimeTicks::Now(); |
| } |
| |
| NotifyBlockingStateChanged(); |
| } |
| |
| void ScreenCaptureNotificationBlocker::ReportSessionMetrics(bool revealed) { |
| auto elapsed_time = |
| base::TimeTicks::Now() - last_screen_capture_session_start_time_; |
| base::UmaHistogramLongTimes( |
| base::StrCat({"Notifications.Blocker.ScreenCapture.", |
| revealed ? "RevealDuration" : "SessionDuration"}), |
| elapsed_time); |
| |
| if (reported_session_metrics_) |
| return; |
| |
| RecordScreenCaptureCount("MutedCount", muted_notification_count_); |
| RecordScreenCaptureCount("ReplacedCount", replaced_notification_count_); |
| RecordScreenCaptureCount("ClosedCount", closed_notification_count_); |
| RecordScreenCaptureCount("SnoozedCount", snoozed_notification_count_); |
| |
| reported_session_metrics_ = true; |
| } |
| |
| void ScreenCaptureNotificationBlocker::DisplayMuteNotification() { |
| int total_notification_count = |
| muted_notification_count_ + replaced_notification_count_; |
| |
| message_center::RichNotificationData rich_notification_data; |
| rich_notification_data.renotify = true; |
| |
| if (base::FeatureList::IsEnabled(features::kMuteNotificationSnoozeAction)) { |
| rich_notification_data.buttons.emplace_back( |
| l10n_util::GetStringUTF16(IDS_NOTIFICATION_MUTED_ACTION_SNOOZE)); |
| } |
| rich_notification_data.buttons.emplace_back(l10n_util::GetPluralStringFUTF16( |
| IDS_NOTIFICATION_MUTED_ACTION_SHOW, total_notification_count)); |
| |
| message_center::Notification notification( |
| message_center::NOTIFICATION_TYPE_SIMPLE, kMuteNotificationId, |
| l10n_util::GetPluralStringFUTF16(IDS_NOTIFICATION_MUTED_TITLE, |
| total_notification_count), |
| l10n_util::GetStringUTF16(IDS_NOTIFICATION_MUTED_MESSAGE), |
| /*icon=*/ui::ImageModel(), |
| /*display_source=*/std::u16string(), |
| /*origin_url=*/GURL(), message_center::NotifierId(), |
| rich_notification_data, |
| /*delegate=*/nullptr); |
| |
| notification_display_service_->Display( |
| NotificationHandler::Type::NOTIFICATIONS_MUTED, notification, |
| /*metadata=*/nullptr); |
| |
| last_mute_notification_time_ = base::TimeTicks::Now(); |
| } |
| |
| void ScreenCaptureNotificationBlocker::CloseMuteNotification() { |
| notification_display_service_->Close( |
| NotificationHandler::Type::NOTIFICATIONS_MUTED, kMuteNotificationId); |
| } |