[Messages] Migrate notification blocked infobar to messages

This Cl only includes the changes of migrating the infobar
to message ui. The changes of introducing the dialog
will be in a followed up CL.

Mock: https://screenshot.googleplex.com/4upv5eUL6sze7ac
Screenshot: https://hsv.googleplex.com/4876989703913472

Bug: 1230927
Change-Id: I88f1ee7fadc8fc294e4813245b3e26719934cf48
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3105005
Commit-Queue: Lijin Shen <[email protected]>
Reviewed-by: Balazs Engedy <[email protected]>
Reviewed-by: Pavel Yatsuk <[email protected]>
Cr-Commit-Position: refs/heads/main@{#941988}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3b3b56bf..0ebcc0de 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3243,6 +3243,8 @@
       "payments/android/service_worker_payment_app_bridge.cc",
       "permissions/grouped_permission_infobar_delegate_android.cc",
       "permissions/grouped_permission_infobar_delegate_android.h",
+      "permissions/notification_blocked_message_delegate_android.cc",
+      "permissions/notification_blocked_message_delegate_android.h",
       "permissions/permission_update_infobar_delegate_android.cc",
       "permissions/permission_update_infobar_delegate_android.h",
       "permissions/permission_update_message_controller_android.cc",
diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index e85b82d..677cd61 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -49,10 +49,12 @@
 #include "chrome/browser/android/resource_mapper.h"
 #include "chrome/browser/android/search_permissions/search_permissions_service.h"
 #include "chrome/browser/permissions/grouped_permission_infobar_delegate_android.h"
+#include "chrome/browser/permissions/notification_blocked_message_delegate_android.h"
 #include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
 #include "chrome/browser/permissions/permission_update_message_controller_android.h"
 #include "components/infobars/content/content_infobar_manager.h"
 #include "components/messages/android/messages_feature.h"
+#include "components/permissions/permission_request_manager.h"
 #else
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/permission_bubble/permission_prompt.h"
@@ -65,6 +67,20 @@
 #include "components/user_manager/user_manager.h"
 #endif
 
+namespace {
+
+#if defined(OS_ANDROID)
+bool ShouldUseQuietUI(content::WebContents* web_contents,
+                      ContentSettingsType type) {
+  auto* manager =
+      permissions::PermissionRequestManager::FromWebContents(web_contents);
+  return type == ContentSettingsType::NOTIFICATIONS &&
+         manager->ShouldCurrentRequestUseQuietUI();
+}
+#endif
+
+}  // namespace
+
 // static
 ChromePermissionsClient* ChromePermissionsClient::GetInstance() {
   static base::NoDestructor<ChromePermissionsClient> instance;
@@ -389,15 +405,32 @@
     base::WeakPtr<permissions::PermissionPromptAndroid> prompt) {
   infobars::ContentInfoBarManager* infobar_manager =
       infobars::ContentInfoBarManager::FromWebContents(web_contents);
-  if (infobar_manager &&
-      GroupedPermissionInfoBarDelegate::ShouldShowMiniInfobar(web_contents,
-                                                              type)) {
+  if (infobar_manager && ShouldUseQuietUI(web_contents, type)) {
     return GroupedPermissionInfoBarDelegate::Create(std::move(prompt),
                                                     infobar_manager);
   }
   return nullptr;
 }
 
+messages::MessageWrapper* ChromePermissionsClient::MaybeCreateMessageUI(
+    content::WebContents* web_contents,
+    ContentSettingsType type,
+    base::WeakPtr<permissions::PermissionPromptAndroid> prompt) {
+  if (messages::IsNotificationBlockedMessagesUiEnabled() &&
+      ShouldUseQuietUI(web_contents, type)) {
+    NotificationBlockedMessageDelegate::CreateForWebContents(web_contents);
+    auto* notification_blocked_message_delegate =
+        NotificationBlockedMessageDelegate::FromWebContents(web_contents);
+    auto delegate =
+        std::make_unique<NotificationBlockedMessageDelegate::Delegate>(
+            std::move(prompt));
+    return notification_blocked_message_delegate->ShowMessage(
+        std::move(delegate));
+  }
+
+  return nullptr;
+}
+
 void ChromePermissionsClient::RepromptForAndroidPermissions(
     content::WebContents* web_contents,
     const std::vector<ContentSettingsType>& content_settings_types,
diff --git a/chrome/browser/permissions/chrome_permissions_client.h b/chrome/browser/permissions/chrome_permissions_client.h
index 61ea22316..3782d53 100644
--- a/chrome/browser/permissions/chrome_permissions_client.h
+++ b/chrome/browser/permissions/chrome_permissions_client.h
@@ -89,6 +89,10 @@
       content::WebContents* web_contents,
       ContentSettingsType type,
       base::WeakPtr<permissions::PermissionPromptAndroid> prompt) override;
+  messages::MessageWrapper* MaybeCreateMessageUI(
+      content::WebContents* web_contents,
+      ContentSettingsType type,
+      base::WeakPtr<permissions::PermissionPromptAndroid> prompt) override;
   void RepromptForAndroidPermissions(
       content::WebContents* web_contents,
       const std::vector<ContentSettingsType>& content_settings_types,
diff --git a/chrome/browser/permissions/grouped_permission_infobar_delegate_android.cc b/chrome/browser/permissions/grouped_permission_infobar_delegate_android.cc
index 622cf8eb..dab833b 100644
--- a/chrome/browser/permissions/grouped_permission_infobar_delegate_android.cc
+++ b/chrome/browser/permissions/grouped_permission_infobar_delegate_android.cc
@@ -20,7 +20,6 @@
 #include "components/permissions/permission_util.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/elide_url.h"
-#include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/strings/grit/ui_strings.h"
 
@@ -230,16 +229,6 @@
   return true;
 }
 
-// static
-bool GroupedPermissionInfoBarDelegate::ShouldShowMiniInfobar(
-    content::WebContents* web_contents,
-    ContentSettingsType type) {
-  auto* manager =
-      permissions::PermissionRequestManager::FromWebContents(web_contents);
-  return type == ContentSettingsType::NOTIFICATIONS &&
-         manager->ShouldCurrentRequestUseQuietUI();
-}
-
 GroupedPermissionInfoBarDelegate::GroupedPermissionInfoBarDelegate(
     const base::WeakPtr<permissions::PermissionPromptAndroid>&
         permission_prompt,
diff --git a/chrome/browser/permissions/grouped_permission_infobar_delegate_android.h b/chrome/browser/permissions/grouped_permission_infobar_delegate_android.h
index 90efdbb2..10dcfd6 100644
--- a/chrome/browser/permissions/grouped_permission_infobar_delegate_android.h
+++ b/chrome/browser/permissions/grouped_permission_infobar_delegate_android.h
@@ -15,10 +15,6 @@
 class ContentInfoBarManager;
 }
 
-namespace content {
-class WebContents;
-}
-
 namespace permissions {
 class PermissionPromptAndroid;
 }
@@ -68,10 +64,6 @@
   bool Accept() override;
   bool Cancel() override;
 
-  // Returns true if we should show the permission request as a mini-infobar.
-  static bool ShouldShowMiniInfobar(content::WebContents* web_contents,
-                                    ContentSettingsType type);
-
  private:
   GroupedPermissionInfoBarDelegate(
       const base::WeakPtr<permissions::PermissionPromptAndroid>&
diff --git a/chrome/browser/permissions/notification_blocked_message_delegate_android.cc b/chrome/browser/permissions/notification_blocked_message_delegate_android.cc
new file mode 100644
index 0000000..e0e6d43
--- /dev/null
+++ b/chrome/browser/permissions/notification_blocked_message_delegate_android.cc
@@ -0,0 +1,133 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/permissions/notification_blocked_message_delegate_android.h"
+
+#include "chrome/browser/android/android_theme_resources.h"
+#include "chrome/browser/android/resource_mapper.h"
+#include "chrome/browser/permissions/quiet_notification_permission_ui_config.h"
+#include "chrome/browser/permissions/quiet_notification_permission_ui_state.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/messages/android/message_dispatcher_bridge.h"
+#include "components/permissions/android/permission_prompt_android.h"
+#include "components/permissions/permission_request.h"
+#include "components/permissions/permission_request_manager.h"
+#include "components/permissions/permission_ui_selector.h"
+#include "components/permissions/permission_util.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/strings/grit/ui_strings.h"
+
+NotificationBlockedMessageDelegate::~NotificationBlockedMessageDelegate() {
+  DismissInternal();
+}
+
+NotificationBlockedMessageDelegate::NotificationBlockedMessageDelegate(
+    content::WebContents* web_contents)
+    : web_contents_(web_contents) {}
+
+messages::MessageWrapper* NotificationBlockedMessageDelegate::ShowMessage(
+    std::unique_ptr<Delegate> delegate) {
+  if (message_) {
+    // Don't show new message UI until the current message has been destroyed,
+    // such as when prompt dialog has been closed or accpeted.
+    return nullptr;
+  }
+  delegate_ = std::move(delegate);
+  message_ = std::make_unique<messages::MessageWrapper>(
+      messages::MessageIdentifier::NOTIFICATION_BLOCKED,
+      base::BindOnce(
+          &NotificationBlockedMessageDelegate::HandlePrimaryActionClick,
+          base::Unretained(this)),
+      base::BindOnce(&NotificationBlockedMessageDelegate::HandleDismissCallback,
+                     base::Unretained(this)));
+  message_->SetTitle(l10n_util::GetStringUTF16(
+      IDS_NOTIFICATION_QUIET_PERMISSION_INFOBAR_TITLE));
+
+  // IDS_OK: notification will still be blocked if primary button is clicked.
+  message_->SetPrimaryButtonText(l10n_util::GetStringUTF16(IDS_OK));
+  message_->SetIconResourceId(ResourceMapper::MapToJavaDrawableId(
+      IDR_ANDROID_INFOBAR_NOTIFICATIONS_OFF));
+  message_->SetSecondaryIconResourceId(
+      ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SETTINGS));
+  message_->SetSecondaryButtonMenuText(
+      l10n_util::GetStringUTF16(IDS_NOTIFICATION_BUTTON_MANAGE));
+
+  message_->SetSecondaryActionCallback(
+      base::BindOnce(&NotificationBlockedMessageDelegate::HandleManageClick,
+                     base::Unretained(this)));
+  messages::MessageDispatcherBridge::Get()->EnqueueMessage(
+      message_.get(), web_contents_, messages::MessageScopeType::NAVIGATION,
+      messages::MessagePriority::kNormal);
+  return message_.get();
+}
+
+void NotificationBlockedMessageDelegate::HandlePrimaryActionClick() {
+  if (!delegate_ || delegate_->IsPromptDestroyed())
+    return;
+
+  DCHECK(delegate_->ShouldUseQuietUI());
+  delegate_->Deny();
+}
+
+void NotificationBlockedMessageDelegate::HandleManageClick() {
+  // TODO(crbug.com/1230927): Implement the flow of showing dialogs
+}
+
+void NotificationBlockedMessageDelegate::HandleDismissCallback(
+    messages::DismissReason reason) {
+  // When message is dismissed by secondary action, |permission_prompt_| should
+  // be reset when the dialog is dismissed.
+  if (reason != messages::DismissReason::SECONDARY_ACTION && delegate_ &&
+      !delegate_->IsPromptDestroyed()) {
+    delegate_->Closing();
+  }
+  delegate_.reset();
+  message_.reset();
+}
+
+void NotificationBlockedMessageDelegate::DismissInternal() {
+  if (message_) {
+    messages::MessageDispatcherBridge::Get()->DismissMessage(
+        message_.get(), messages::DismissReason::UNKNOWN);
+  }
+}
+
+void NotificationBlockedMessageDelegate::Delegate::Accept() {}
+
+void NotificationBlockedMessageDelegate::Delegate::Deny() {
+  if (!permission_prompt_)
+    return;
+  permission_prompt_->Deny();
+}
+
+void NotificationBlockedMessageDelegate::Delegate::Closing() {
+  if (!permission_prompt_)
+    return;
+  permission_prompt_->Closing();
+  permission_prompt_.reset();
+}
+
+bool NotificationBlockedMessageDelegate::Delegate::IsPromptDestroyed() {
+  return !permission_prompt_;
+}
+
+bool NotificationBlockedMessageDelegate::Delegate::ShouldUseQuietUI() {
+  return permission_prompt_->ShouldCurrentRequestUseQuietUI();
+}
+
+NotificationBlockedMessageDelegate::Delegate::~Delegate() {
+  Closing();
+}
+
+NotificationBlockedMessageDelegate::Delegate::Delegate() {}
+
+NotificationBlockedMessageDelegate::Delegate::Delegate(
+    const base::WeakPtr<permissions::PermissionPromptAndroid>&
+        permission_prompt)
+    : permission_prompt_(permission_prompt) {}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(NotificationBlockedMessageDelegate);
diff --git a/chrome/browser/permissions/notification_blocked_message_delegate_android.h b/chrome/browser/permissions/notification_blocked_message_delegate_android.h
new file mode 100644
index 0000000..2fc17bc
--- /dev/null
+++ b/chrome/browser/permissions/notification_blocked_message_delegate_android.h
@@ -0,0 +1,72 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PERMISSIONS_NOTIFICATION_BLOCKED_MESSAGE_DELEGATE_ANDROID_H_
+#define CHROME_BROWSER_PERMISSIONS_NOTIFICATION_BLOCKED_MESSAGE_DELEGATE_ANDROID_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "components/messages/android/message_enums.h"
+#include "components/messages/android/message_wrapper.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace permissions {
+class PermissionPromptAndroid;
+}
+
+// A message ui that displays a notification permission request, which is an
+// alternative ui to the mini infobar.
+class NotificationBlockedMessageDelegate
+    : public content::WebContentsUserData<NotificationBlockedMessageDelegate> {
+ public:
+  // Delegate to mock out the |PermissionPromptAndroid| for testing.
+  class Delegate {
+   public:
+    Delegate();
+    Delegate(const base::WeakPtr<permissions::PermissionPromptAndroid>&
+                 permission_prompt);
+    virtual ~Delegate();
+    virtual void Accept();
+    virtual void Deny();
+    virtual void Closing();
+    virtual bool IsPromptDestroyed();
+    virtual bool ShouldUseQuietUI();
+
+   private:
+    base::WeakPtr<permissions::PermissionPromptAndroid> permission_prompt_;
+  };
+
+  ~NotificationBlockedMessageDelegate() override;
+
+  // Returns pointer to the message wrapper if a new message UIs has been
+  // created and will be shown. Returns nullptr if not created.
+  messages::MessageWrapper* ShowMessage(std::unique_ptr<Delegate> delegate);
+
+ private:
+  friend class content::WebContentsUserData<NotificationBlockedMessageDelegate>;
+  friend class NotificationBlockedMessageDelegateAndroidTest;
+
+  explicit NotificationBlockedMessageDelegate(
+      content::WebContents* web_contents);
+
+  void HandlePrimaryActionClick();
+  void HandleDismissCallback(messages::DismissReason reason);
+  void HandleManageClick();
+
+  void DismissInternal();
+
+  std::unique_ptr<messages::MessageWrapper> message_;
+  std::unique_ptr<Delegate> delegate_;
+  content::WebContents* web_contents_ = nullptr;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+#endif  // CHROME_BROWSER_PERMISSIONS_NOTIFICATION_BLOCKED_MESSAGE_DELEGATE_ANDROID_H_
diff --git a/chrome/browser/permissions/notification_blocked_message_delegate_android_unittest.cc b/chrome/browser/permissions/notification_blocked_message_delegate_android_unittest.cc
new file mode 100644
index 0000000..b45cad3
--- /dev/null
+++ b/chrome/browser/permissions/notification_blocked_message_delegate_android_unittest.cc
@@ -0,0 +1,139 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/permissions/notification_blocked_message_delegate_android.h"
+
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/messages/android/mock_message_dispatcher_bridge.h"
+#include "components/permissions/permission_prompt.h"
+#include "components/permissions/permission_request_manager.h"
+#include "components/permissions/test/mock_permission_prompt_factory.h"
+
+class MockDelegate : public NotificationBlockedMessageDelegate::Delegate {
+ public:
+  ~MockDelegate() override = default;
+  MockDelegate(const base::WeakPtr<permissions::PermissionPromptAndroid>&
+                   permission_prompt) {}
+
+  MOCK_METHOD(void, Accept, (), (override));
+  MOCK_METHOD(void, Deny, (), (override));
+
+  MOCK_METHOD(void, Closing, (), (override));
+  MOCK_METHOD(bool, IsPromptDestroyed, (), (override));
+
+  MOCK_METHOD(bool, ShouldUseQuietUI, (), (override));
+};
+
+class NotificationBlockedMessageDelegateAndroidTest
+    : public ChromeRenderViewHostTestHarness {
+ public:
+  NotificationBlockedMessageDelegateAndroidTest() = default;
+  ~NotificationBlockedMessageDelegateAndroidTest() override = default;
+
+  NotificationBlockedMessageDelegate* GetController() { return controller_; }
+  void ExpectEnqueued() {
+    EXPECT_CALL(message_dispatcher_bridge_, EnqueueMessage);
+  }
+
+  void TriggerDismiss(messages::DismissReason reason) {
+    EXPECT_CALL(message_dispatcher_bridge_, DismissMessage)
+        .WillOnce([&reason](messages::MessageWrapper* message,
+                            messages::DismissReason dismiss_reason) {
+          message->HandleDismissCallback(base::android::AttachCurrentThread(),
+                                         static_cast<int>(reason));
+        });
+    controller_->DismissInternal();
+    EXPECT_EQ(nullptr, controller_->message_.get());
+  }
+
+  void TriggerPrimaryAction() {
+    controller_->HandlePrimaryActionClick();
+    TriggerDismiss(messages::DismissReason::PRIMARY_ACTION);
+  }
+
+  messages::MessageWrapper* GetMessageWrapper() {
+    return controller_->message_.get();
+  }
+
+  std::unique_ptr<MockDelegate> GetMockDelegate() {
+    return std::move(delegate_);
+  }
+
+ protected:
+  void SetUp() override;
+  void TearDown() override;
+
+ private:
+  NotificationBlockedMessageDelegate* controller_;
+  messages::MockMessageDispatcherBridge message_dispatcher_bridge_;
+  std::unique_ptr<MockDelegate> delegate_;
+};
+
+void NotificationBlockedMessageDelegateAndroidTest::SetUp() {
+  content::RenderViewHostTestHarness::SetUp();
+  permissions::PermissionRequestManager::CreateForWebContents(web_contents());
+  NotificationBlockedMessageDelegate::CreateForWebContents(web_contents());
+  controller_ =
+      NotificationBlockedMessageDelegate::FromWebContents(web_contents());
+  messages::MessageDispatcherBridge::SetInstanceForTesting(
+      &message_dispatcher_bridge_);
+  delegate_ = std::make_unique<MockDelegate>(nullptr);
+}
+
+void NotificationBlockedMessageDelegateAndroidTest::TearDown() {
+  messages::MessageDispatcherBridge::SetInstanceForTesting(nullptr);
+  ChromeRenderViewHostTestHarness::TearDown();
+}
+
+TEST_F(NotificationBlockedMessageDelegateAndroidTest, DismissByTimeout) {
+  auto delegate = GetMockDelegate();
+  EXPECT_CALL(*delegate, IsPromptDestroyed)
+      .WillRepeatedly(testing::Return(false));
+
+  EXPECT_CALL(*delegate, Closing);
+  EXPECT_CALL(*delegate, Accept).Times(0);
+  EXPECT_CALL(*delegate, Deny).Times(0);
+
+  ExpectEnqueued();
+
+  GetController()->ShowMessage(std::move(delegate));
+  TriggerDismiss(messages::DismissReason::TIMER);
+  EXPECT_EQ(nullptr, GetMessageWrapper());
+}
+
+TEST_F(NotificationBlockedMessageDelegateAndroidTest, DismissByPrimaryAction) {
+  auto delegate = GetMockDelegate();
+  EXPECT_CALL(*delegate, IsPromptDestroyed)
+      .WillRepeatedly(testing::Return(false));
+  EXPECT_CALL(*delegate, ShouldUseQuietUI)
+      .WillRepeatedly(testing::Return(true));
+
+  EXPECT_CALL(*delegate, Closing);
+  EXPECT_CALL(*delegate, Accept).Times(0);
+  EXPECT_CALL(*delegate, Deny);
+
+  ExpectEnqueued();
+
+  GetController()->ShowMessage(std::move(delegate));
+  TriggerPrimaryAction();
+  EXPECT_EQ(nullptr, GetMessageWrapper());
+}
+
+TEST_F(NotificationBlockedMessageDelegateAndroidTest,
+       DismissByPrimaryActionWhenPromptDestroyed) {
+  auto delegate = GetMockDelegate();
+  EXPECT_CALL(*delegate, IsPromptDestroyed)
+      .WillRepeatedly(testing::Return(true));
+  EXPECT_CALL(*delegate, ShouldUseQuietUI)
+      .WillRepeatedly(testing::Return(true));
+
+  EXPECT_CALL(*delegate, Closing).Times(0);
+  EXPECT_CALL(*delegate, Accept).Times(0);
+  EXPECT_CALL(*delegate, Deny).Times(0);
+
+  ExpectEnqueued();
+  GetController()->ShowMessage(std::move(delegate));
+  TriggerPrimaryAction();
+  EXPECT_EQ(nullptr, GetMessageWrapper());
+}
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 43c6f14a..9a4a84b9 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4939,6 +4939,7 @@
       "../browser/notifications/notification_channels_provider_android_unittest.cc",
       "../browser/optimization_guide/android/optimization_guide_tab_url_provider_android_unittest.cc",
       "../browser/password_manager/android/password_ui_view_android_unittest.cc",
+      "../browser/permissions/notification_blocked_message_delegate_android_unittest.cc",
       "../browser/search/contextual_search_policy_handler_android_unittest.cc",
       "../browser/translate/android/translate_bridge_unittest.cc",
       "../browser/ui/android/autofill/save_card_message_controller_android_unittest.cc",