snooping_protection_notification_blocker.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. // Copyright 2021 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. #include "ash/system/human_presence/snooping_protection_notification_blocker.h"
  5. #include <string>
  6. #include "ash/constants/ash_pref_names.h"
  7. #include "ash/public/cpp/notification_utils.h"
  8. #include "ash/public/cpp/session/session_observer.h"
  9. #include "ash/public/cpp/system_tray_client.h"
  10. #include "ash/resources/vector_icons/vector_icons.h"
  11. #include "ash/root_window_controller.h"
  12. #include "ash/session/session_controller_impl.h"
  13. #include "ash/shell.h"
  14. #include "ash/strings/grit/ash_strings.h"
  15. #include "ash/system/human_presence/human_presence_metrics.h"
  16. #include "ash/system/human_presence/snooping_protection_controller.h"
  17. #include "ash/system/human_presence/snooping_protection_notification_blocker_internal.h"
  18. #include "ash/system/model/system_tray_model.h"
  19. #include "ash/system/network/sms_observer.h"
  20. #include "ash/system/status_area_widget.h"
  21. #include "ash/system/unified/unified_system_tray.h"
  22. #include "base/bind.h"
  23. #include "base/check_op.h"
  24. #include "base/memory/weak_ptr.h"
  25. #include "base/metrics/histogram_functions.h"
  26. #include "base/no_destructor.h"
  27. #include "base/notreached.h"
  28. #include "base/strings/string_util.h"
  29. #include "chromeos/ash/components/dbus/hps/hps_service.pb.h"
  30. #include "components/prefs/pref_change_registrar.h"
  31. #include "components/prefs/pref_service.h"
  32. #include "ui/base/l10n/l10n_util.h"
  33. #include "ui/message_center/message_center.h"
  34. #include "ui/message_center/public/cpp/notification.h"
  35. namespace ash {
  36. namespace {
  37. namespace metrics = ash::snooping_protection_metrics;
  38. constexpr char kNotifierId[] = "hps-notify";
  39. // Returns the capitalized version of an improper-noun notifier title, or the
  40. // unchanged title if it is a proper noun.
  41. std::u16string GetCapitalizedNotifierTitle(const std::u16string& title) {
  42. static base::NoDestructor<std::map<std::u16string, std::u16string>>
  43. kCapitalizedTitles(
  44. {{l10n_util::GetStringUTF16(
  45. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_APP_TITLE_LOWER),
  46. l10n_util::GetStringUTF16(
  47. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_APP_TITLE_UPPER)},
  48. {l10n_util::GetStringUTF16(
  49. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_SYSTEM_TITLE_LOWER),
  50. l10n_util::GetStringUTF16(
  51. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_SYSTEM_TITLE_UPPER)},
  52. {l10n_util::GetStringUTF16(
  53. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_WEB_TITLE_LOWER),
  54. l10n_util::GetStringUTF16(
  55. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_WEB_TITLE_UPPER)}});
  56. const auto it = kCapitalizedTitles->find(title);
  57. return it == kCapitalizedTitles->end() ? title : it->second;
  58. }
  59. } // namespace
  60. namespace hps_internal {
  61. // Returns the popup message listing the correct notifier titles and the correct
  62. // number of titles.
  63. std::u16string GetTitlesBlockedMessage(
  64. const std::vector<std::u16string>& titles) {
  65. DCHECK(!titles.empty());
  66. const std::u16string& first_title = GetCapitalizedNotifierTitle(titles[0]);
  67. switch (titles.size()) {
  68. case 1:
  69. return l10n_util::GetStringFUTF16(
  70. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_MESSAGE_1, first_title);
  71. case 2:
  72. return l10n_util::GetStringFUTF16(
  73. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_MESSAGE_2, first_title,
  74. titles[1]);
  75. default:
  76. return l10n_util::GetStringFUTF16(
  77. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_MESSAGE_3_PLUS,
  78. first_title, titles[1]);
  79. }
  80. }
  81. } // namespace hps_internal
  82. SnoopingProtectionNotificationBlocker::SnoopingProtectionNotificationBlocker(
  83. message_center::MessageCenter* message_center,
  84. SnoopingProtectionController* controller)
  85. : NotificationBlocker(message_center),
  86. message_center_(message_center),
  87. controller_(controller) {
  88. controller_observation_.Observe(controller_);
  89. // Session controller is instantiated before us in the shell.
  90. SessionControllerImpl* session_controller =
  91. Shell::Get()->session_controller();
  92. DCHECK(session_controller);
  93. session_observation_.Observe(session_controller);
  94. message_center_observation_.Observe(message_center_);
  95. UpdateInfoNotificationIfNecessary();
  96. }
  97. SnoopingProtectionNotificationBlocker::
  98. ~SnoopingProtectionNotificationBlocker() = default;
  99. void SnoopingProtectionNotificationBlocker::OnActiveUserPrefServiceChanged(
  100. PrefService* pref_service) {
  101. OnBlockingActiveChanged();
  102. // Listen to changes to the user's preferences.
  103. pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  104. pref_change_registrar_->Init(pref_service);
  105. pref_change_registrar_->Add(
  106. prefs::kSnoopingProtectionNotificationSuppressionEnabled,
  107. base::BindRepeating(
  108. &SnoopingProtectionNotificationBlocker::OnBlockingPrefChanged,
  109. weak_ptr_factory_.GetWeakPtr()));
  110. }
  111. void SnoopingProtectionNotificationBlocker::OnBlockingActiveChanged() {
  112. NotifyBlockingStateChanged();
  113. UpdateInfoNotificationIfNecessary();
  114. }
  115. void SnoopingProtectionNotificationBlocker::OnBlockingPrefChanged() {
  116. DCHECK(pref_change_registrar_);
  117. DCHECK(pref_change_registrar_->prefs());
  118. const bool pref_enabled = pref_change_registrar_->prefs()->GetBoolean(
  119. prefs::kSnoopingProtectionNotificationSuppressionEnabled);
  120. base::UmaHistogramBoolean(
  121. metrics::kNotificationSuppressionEnabledHistogramName, pref_enabled);
  122. OnBlockingActiveChanged();
  123. }
  124. bool SnoopingProtectionNotificationBlocker::ShouldShowNotificationAsPopup(
  125. const message_center::Notification& notification) const {
  126. // If we've populated our info popup, we're definitely hiding some other
  127. // notifications and need to inform the user.
  128. if (notification.id() == kInfoNotificationId)
  129. return true;
  130. // We don't suppress any popups while inactive.
  131. if (!BlockingActive())
  132. return true;
  133. // Always show important system notifications.
  134. // An exception: SMS content is personal information.
  135. return notification.notifier_id().type ==
  136. message_center::NotifierType::SYSTEM_COMPONENT &&
  137. !base::StartsWith(notification.id(), SmsObserver::kNotificationPrefix);
  138. }
  139. void SnoopingProtectionNotificationBlocker::OnSnoopingStatusChanged(
  140. bool /*snooper*/) {
  141. // Need to reevaluate blocking for (i.e. un/hide) all notifications when a
  142. // snooper appears. This also catches disabling the snooping feature all
  143. // together, since that is translated to a "no snooper" event by the
  144. // controller.
  145. OnBlockingActiveChanged();
  146. }
  147. void SnoopingProtectionNotificationBlocker::
  148. OnSnoopingProtectionControllerDestroyed() {
  149. controller_observation_.Reset();
  150. }
  151. void SnoopingProtectionNotificationBlocker::OnNotificationAdded(
  152. const std::string& notification_id) {
  153. if (notification_id != kInfoNotificationId)
  154. UpdateInfoNotificationIfNecessary();
  155. }
  156. void SnoopingProtectionNotificationBlocker::OnNotificationRemoved(
  157. const std::string& notification_id,
  158. bool /*by_user*/) {
  159. if (notification_id == kInfoNotificationId)
  160. info_popup_exists_ = false;
  161. else
  162. UpdateInfoNotificationIfNecessary();
  163. }
  164. void SnoopingProtectionNotificationBlocker::OnNotificationUpdated(
  165. const std::string& notification_id) {
  166. if (notification_id != kInfoNotificationId)
  167. UpdateInfoNotificationIfNecessary();
  168. }
  169. void SnoopingProtectionNotificationBlocker::OnBlockingStateChanged(
  170. message_center::NotificationBlocker* blocker) {
  171. if (blocker != this)
  172. UpdateInfoNotificationIfNecessary();
  173. }
  174. void SnoopingProtectionNotificationBlocker::Close(bool by_user) {}
  175. void SnoopingProtectionNotificationBlocker::Click(
  176. const absl::optional<int>& button_index,
  177. const absl::optional<std::u16string>& reply) {
  178. if (!button_index.has_value())
  179. return;
  180. switch (button_index.value()) {
  181. // Show notifications button
  182. case 0:
  183. Shell::Get()
  184. ->GetPrimaryRootWindowController()
  185. ->GetStatusAreaWidget()
  186. ->unified_system_tray()
  187. ->ShowBubble();
  188. break;
  189. // Show privacy settings button
  190. case 1:
  191. Shell::Get()->system_tray_model()->client()->ShowSmartPrivacySettings();
  192. break;
  193. default:
  194. NOTREACHED() << "Unknown button index value";
  195. }
  196. }
  197. bool SnoopingProtectionNotificationBlocker::BlockingActive() const {
  198. // Never block if the feature is disabled.
  199. const PrefService* const pref_service =
  200. Shell::Get()->session_controller()->GetActivePrefService();
  201. if (!pref_service ||
  202. !pref_service->GetBoolean(
  203. prefs::kSnoopingProtectionNotificationSuppressionEnabled))
  204. return false;
  205. // Only block when there isn't a snooper detected.
  206. return controller_->SnooperPresent();
  207. }
  208. void SnoopingProtectionNotificationBlocker::
  209. UpdateInfoNotificationIfNecessary() {
  210. // Collect the IDs whose popups would be shown but for us.
  211. std::set<std::string> new_blocked_popups;
  212. if (BlockingActive()) {
  213. const message_center::NotificationList::PopupNotifications popup_list =
  214. message_center_->GetPopupNotificationsWithoutBlocker(*this);
  215. for (const auto* value : popup_list) {
  216. if (!ShouldShowNotificationAsPopup(*value))
  217. new_blocked_popups.insert(value->id());
  218. }
  219. }
  220. if (blocked_popups_ == new_blocked_popups)
  221. return;
  222. blocked_popups_ = new_blocked_popups;
  223. if (blocked_popups_.empty()) {
  224. // No-op if the user has already closed the popup.
  225. message_center_->RemoveNotification(kInfoNotificationId, /*by_user=*/false);
  226. info_popup_exists_ = false;
  227. } else if (info_popup_exists_) {
  228. message_center_->UpdateNotification(kInfoNotificationId,
  229. CreateInfoNotification());
  230. message_center_->ResetSinglePopup(kInfoNotificationId);
  231. } else {
  232. message_center_->AddNotification(CreateInfoNotification());
  233. message_center_->ResetSinglePopup(kInfoNotificationId);
  234. info_popup_exists_ = true;
  235. }
  236. }
  237. std::unique_ptr<message_center::Notification>
  238. SnoopingProtectionNotificationBlocker::CreateInfoNotification() const {
  239. // Create a list of popup titles in descending order of recentness and with no
  240. // duplicates.
  241. std::vector<std::u16string> titles;
  242. std::set<std::u16string> seen_titles;
  243. for (const message_center::Notification* notification :
  244. message_center_->GetPopupNotificationsWithoutBlocker(*this)) {
  245. const std::string& id = notification->id();
  246. if (blocked_popups_.find(id) == blocked_popups_.end())
  247. continue;
  248. // Use a human readable-title (e.g. "Web" vs "https://somesite.com:443").
  249. const std::u16string& title =
  250. hps_internal::GetNotifierTitle<apps::AppRegistryCacheWrapper>(
  251. notification->notifier_id(),
  252. Shell::Get()->session_controller()->GetActiveAccountId());
  253. if (seen_titles.find(title) != seen_titles.end())
  254. continue;
  255. titles.push_back(title);
  256. seen_titles.insert(title);
  257. }
  258. // TODO(1276903): find out how to make the correct notification count.
  259. message_center::RichNotificationData notification_data;
  260. notification_data.buttons.push_back(
  261. message_center::ButtonInfo(l10n_util::GetStringUTF16(
  262. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_SHOW_BUTTON_TEXT)));
  263. notification_data.buttons.push_back(
  264. message_center::ButtonInfo(l10n_util::GetStringUTF16(
  265. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_SETTINGS_BUTTON_TEXT)));
  266. auto notification = CreateSystemNotification(
  267. message_center::NOTIFICATION_TYPE_SIMPLE, kInfoNotificationId,
  268. l10n_util::GetStringUTF16(
  269. IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_TITLE),
  270. hps_internal::GetTitlesBlockedMessage(titles),
  271. /*display_source=*/std::u16string(), /*origin_url=*/GURL(),
  272. message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
  273. kNotifierId,
  274. NotificationCatalogName::kHPSNotify),
  275. notification_data,
  276. base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
  277. weak_ptr_factory_.GetWeakPtr()),
  278. kSystemTraySnoopingProtectionIcon,
  279. message_center::SystemNotificationWarningLevel::NORMAL);
  280. return notification;
  281. }
  282. } // namespace ash