Stop duplicates WebUSB notification

This change adds a list of notifications that are currently displayed and checks
that list for duplicates before displaying a new notification.

Bug: 789362
Change-Id: I1f8025dbb237da35b088de5bd6f21e80a1bbec52
Reviewed-on: https://chromium-review.googlesource.com/c/1373436
Commit-Queue: James Hollyer <jameshollyer@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#619541}
diff --git a/chrome/browser/usb/web_usb_detector.cc b/chrome/browser/usb/web_usb_detector.cc
index 4d92dfc..50d1ebf 100644
--- a/chrome/browser/usb/web_usb_detector.cc
+++ b/chrome/browser/usb/web_usb_detector.cc
@@ -96,9 +96,11 @@
 class WebUsbNotificationDelegate : public TabStripModelObserver,
                                    public message_center::NotificationDelegate {
  public:
-  WebUsbNotificationDelegate(const GURL& landing_page,
+  WebUsbNotificationDelegate(base::WeakPtr<WebUsbDetector> detector,
+                             const GURL& landing_page,
                              const std::string& notification_id)
-      : landing_page_(landing_page),
+      : detector_(std::move(detector)),
+        landing_page_(landing_page),
         notification_id_(notification_id),
         disposition_(WEBUSB_NOTIFICATION_CLOSED),
         browser_tab_strip_tracker_(this, nullptr, nullptr) {
@@ -157,11 +159,14 @@
     RecordNotificationClosure(disposition_);
 
     browser_tab_strip_tracker_.StopObservingAndSendOnBrowserRemoved();
+    if (detector_)
+      detector_->RemoveNotification(notification_id_);
   }
 
  private:
   ~WebUsbNotificationDelegate() override = default;
 
+  base::WeakPtr<WebUsbDetector> detector_;
   GURL landing_page_;
   std::string notification_id_;
   WebUsbNotificationClosed disposition_;
@@ -172,7 +177,7 @@
 
 }  // namespace
 
-WebUsbDetector::WebUsbDetector() : client_binding_(this) {}
+WebUsbDetector::WebUsbDetector() : client_binding_(this), weak_factory_(this) {}
 
 WebUsbDetector::~WebUsbDetector() {}
 
@@ -223,6 +228,9 @@
     return;
   }
 
+  if (IsDisplayingNotification(landing_page))
+    return;
+
   std::string notification_id = device_info->guid;
 
   message_center::RichNotificationData rich_notification_data;
@@ -240,10 +248,25 @@
       message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                  kNotifierWebUsb),
       rich_notification_data,
-      base::MakeRefCounted<WebUsbNotificationDelegate>(landing_page,
-                                                       notification_id));
+      base::MakeRefCounted<WebUsbNotificationDelegate>(
+          weak_factory_.GetWeakPtr(), landing_page, notification_id));
   notification.SetSystemPriority();
   SystemNotificationHelper::GetInstance()->Display(notification);
+  open_notifications_by_id_[notification_id] = landing_page;
+}
+
+bool WebUsbDetector::IsDisplayingNotification(const GURL& url) {
+  for (const auto& map_entry : open_notifications_by_id_) {
+    const GURL& entry_url = map_entry.second;
+    if (url == entry_url)
+      return true;
+  }
+
+  return false;
+}
+
+void WebUsbDetector::RemoveNotification(const std::string& id) {
+  open_notifications_by_id_.erase(id);
 }
 
 void WebUsbDetector::OnDeviceRemoved(
diff --git a/chrome/browser/usb/web_usb_detector.h b/chrome/browser/usb/web_usb_detector.h
index af6ea2a7..96ad098 100644
--- a/chrome/browser/usb/web_usb_detector.h
+++ b/chrome/browser/usb/web_usb_detector.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_USB_WEB_USB_DETECTOR_H_
 #define CHROME_BROWSER_USB_WEB_USB_DETECTOR_H_
 
+#include <map>
+
 #include "base/macros.h"
 #include "device/usb/public/mojom/device_manager.mojom.h"
 #include "mojo/public/cpp/bindings/associated_binding.h"
@@ -19,6 +21,7 @@
 
   void SetDeviceManagerForTesting(
       device::mojom::UsbDeviceManagerPtr fake_device_manager);
+  void RemoveNotification(const std::string& id);
 
  private:
   // device::mojom::UsbDeviceManagerClient implementation.
@@ -26,12 +29,17 @@
   void OnDeviceRemoved(device::mojom::UsbDeviceInfoPtr device_info) override;
 
   void OnDeviceManagerConnectionError();
+  bool IsDisplayingNotification(const GURL& url);
+
+  std::map<std::string, GURL> open_notifications_by_id_;
 
   // Connection to |device_manager_instance_|.
   device::mojom::UsbDeviceManagerPtr device_manager_;
   mojo::AssociatedBinding<device::mojom::UsbDeviceManagerClient>
       client_binding_;
 
+  base::WeakPtrFactory<WebUsbDetector> weak_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(WebUsbDetector);
 };
 
diff --git a/chrome/browser/usb/web_usb_detector_unittest.cc b/chrome/browser/usb/web_usb_detector_unittest.cc
index c3d34aea..7c16af6 100644
--- a/chrome/browser/usb/web_usb_detector_unittest.cc
+++ b/chrome/browser/usb/web_usb_detector_unittest.cc
@@ -541,7 +541,6 @@
 
 TEST_F(WebUsbDetectorTest, NotificationClickedWhileNoTabUrlIsLandingPage) {
   GURL landing_page_1(kLandingPage_1);
-  GURL landing_page_2(kLandingPage_2);
   auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
       0, 1, "Google", kProductName_1, "002", landing_page_1);
   std::string guid_1 = device_1->guid();
@@ -604,6 +603,103 @@
   EXPECT_FALSE(display_service_->GetNotification(guid_1));
 }
 
+TEST_F(WebUsbDetectorTest, TwoDevicesSameLandingPageAddedRemovedAndAddedAgain) {
+  GURL landing_page_1(kLandingPage_1);
+  auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
+      0, 1, "Google", kProductName_1, "002", landing_page_1);
+  std::string guid_1 = device_1->guid();
+
+  auto device_2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
+      3, 4, "Google", kProductName_2, "005", landing_page_1);
+  std::string guid_2 = device_2->guid();
+
+  Initialize();
+  base::RunLoop().RunUntilIdle();
+
+  device_manager_.AddDevice(device_1);
+  base::RunLoop().RunUntilIdle();
+  base::Optional<message_center::Notification> notification_1 =
+      display_service_->GetNotification(guid_1);
+  ASSERT_TRUE(notification_1);
+  base::string16 expected_title_1 =
+      base::ASCIIToUTF16("Google Product A detected");
+  EXPECT_EQ(expected_title_1, notification_1->title());
+  base::string16 expected_message_1 =
+      base::ASCIIToUTF16("Go to www.google.com to connect.");
+  EXPECT_EQ(expected_message_1, notification_1->message());
+  EXPECT_TRUE(notification_1->delegate() != nullptr);
+
+  device_manager_.AddDevice(device_2);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(display_service_->GetNotification(guid_2));
+
+  device_manager_.RemoveDevice(device_2);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(display_service_->GetNotification(guid_1));
+
+  device_manager_.RemoveDevice(device_1);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(display_service_->GetNotification(guid_1));
+
+  device_manager_.AddDevice(device_2);
+  base::RunLoop().RunUntilIdle();
+  base::Optional<message_center::Notification> notification_2 =
+      display_service_->GetNotification(guid_2);
+  ASSERT_TRUE(notification_2);
+  base::string16 expected_title_2 =
+      base::ASCIIToUTF16("Google Product B detected");
+  EXPECT_EQ(expected_title_2, notification_2->title());
+  base::string16 expected_message_2 =
+      base::ASCIIToUTF16("Go to www.google.com to connect.");
+  EXPECT_EQ(expected_message_2, notification_2->message());
+  EXPECT_TRUE(notification_2->delegate() != nullptr);
+
+  device_manager_.AddDevice(device_1);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(display_service_->GetNotification(guid_1));
+}
+
+TEST_F(
+    WebUsbDetectorTest,
+    DeviceWithSameLandingPageAddedAfterNotificationClickedAndThenNewTabActive) {
+  GURL landing_page_1(kLandingPage_1);
+  GURL landing_page_2(kLandingPage_2);
+  auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
+      0, 1, "Google", kProductName_1, "002", landing_page_1);
+  std::string guid_1 = device_1->guid();
+
+  auto device_2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
+      0, 1, "Google", kProductName_2, "002", landing_page_1);
+  std::string guid_2 = device_2->guid();
+
+  TabStripModel* tab_strip_model = browser()->tab_strip_model();
+
+  base::HistogramTester histogram_tester;
+  Initialize();
+  base::RunLoop().RunUntilIdle();
+
+  device_manager_.AddDevice(device_1);
+  base::RunLoop().RunUntilIdle();
+  base::Optional<message_center::Notification> notification_1 =
+      display_service_->GetNotification(guid_1);
+  ASSERT_TRUE(notification_1);
+  EXPECT_EQ(0, tab_strip_model->count());
+
+  notification_1->delegate()->Click(base::nullopt, base::nullopt);
+  EXPECT_EQ(1, tab_strip_model->count());
+  content::WebContents* web_contents =
+      tab_strip_model->GetWebContentsAt(tab_strip_model->active_index());
+  EXPECT_EQ(landing_page_1, web_contents->GetURL());
+  EXPECT_FALSE(display_service_->GetNotification(guid_1));
+  histogram_tester.ExpectUniqueSample("WebUsb.NotificationClosed", 2, 1);
+
+  AddTab(browser(), landing_page_2);
+
+  device_manager_.AddDevice(device_2);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(display_service_->GetNotification(guid_2));
+}
+
 TEST_F(WebUsbDetectorTest,
        NotificationClickedWhileInactiveTabFuzzyUrlIsLandingPage) {
   GURL landing_page_1(kLandingPage_1);
@@ -637,4 +733,34 @@
   histogram_tester.ExpectUniqueSample("WebUsb.NotificationClosed", 2, 1);
 }
 
+TEST_F(WebUsbDetectorTest,
+       DeviceWithSameLandingPageAddedAfterPageVisitedAndNewTabActive) {
+  GURL landing_page_1(kLandingPage_1);
+  GURL landing_page_2(kLandingPage_2);
+  auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
+      0, 1, "Google", kProductName_1, "002", landing_page_1);
+  std::string guid_1 = device_1->guid();
+
+  auto device_2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
+      0, 1, "Google", kProductName_1, "002", landing_page_1);
+  std::string guid_2 = device_2->guid();
+
+  base::HistogramTester histogram_tester;
+  Initialize();
+  base::RunLoop().RunUntilIdle();
+
+  device_manager_.AddDevice(device_1);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(display_service_->GetNotification(guid_1));
+
+  AddTab(browser(), landing_page_1);
+  EXPECT_FALSE(display_service_->GetNotification(guid_1));
+  histogram_tester.ExpectUniqueSample("WebUsb.NotificationClosed", 3, 1);
+
+  AddTab(browser(), landing_page_2);
+  device_manager_.AddDevice(device_2);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(display_service_->GetNotification(guid_2));
+}
+
 #endif  // !OS_WIN