[Privacy Hub] Refactor software switch notification [2/2]

This is the second in a series of two CLs that re-implements the Privacy
Hub software switch notifications. Camera software switch notification,
microphone software switch notification and the combined notification
will be represented by a same notification object, thus the three
notifications will have same notification ID. This makes sure two or
more of the three notifications can not be in visible simultaneously.

Bug: b:267769814
Change-Id: I550bf06204c5525434678394e80156947ad77c8b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4272268
Reviewed-by: Christoph Schlosser <cschlosser@chromium.org>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Md Shahadat Hossain Shahin <shahinmd@google.com>
Cr-Commit-Position: refs/heads/main@{#1113454}
diff --git a/ash/system/privacy_hub/camera_privacy_switch_controller.cc b/ash/system/privacy_hub/camera_privacy_switch_controller.cc
index e019f6b..4dc1c7e9 100644
--- a/ash/system/privacy_hub/camera_privacy_switch_controller.cc
+++ b/ash/system/privacy_hub/camera_privacy_switch_controller.cc
@@ -65,7 +65,7 @@
           PrivacyHubNotificationDescriptor{
               SensorDisabledNotificationDelegate::SensorSet{},
               IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_TITLE,
-              IDS_PRIVACY_HUB_TURN_OFF_CAMERA_ACTION_BUTTON,
+              std::vector<int>{IDS_PRIVACY_HUB_TURN_OFF_CAMERA_ACTION_BUTTON},
               std::vector<int>{
                   IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_MESSAGE},
               base::MakeRefCounted<PrivacyHubNotificationClickDelegate>(
@@ -130,11 +130,11 @@
 
   if (pref_val == CameraSWPrivacySwitchSetting::kDisabled) {
     camera_used_while_deactivated_ = true;
-    GetPrivacyHubNotificationController()->ShowSensorDisabledNotification(
+    GetPrivacyHubNotificationController()->ShowSoftwareSwitchNotification(
         SensorDisabledNotificationDelegate::Sensor::kCamera);
   } else {
     camera_used_while_deactivated_ = false;
-    GetPrivacyHubNotificationController()->RemoveSensorDisabledNotification(
+    GetPrivacyHubNotificationController()->RemoveSoftwareSwitchNotification(
         SensorDisabledNotificationDelegate::Sensor::kCamera);
   }
 }
@@ -221,14 +221,14 @@
   if (active_applications_using_camera_count_ == 0 &&
       camera_used_while_deactivated_) {
     camera_used_while_deactivated_ = false;
-    GetPrivacyHubNotificationController()->RemoveSensorDisabledNotification(
+    GetPrivacyHubNotificationController()->RemoveSoftwareSwitchNotification(
         SensorDisabledNotificationDelegate::Sensor::kCamera);
   } else if (application_added) {
     camera_used_while_deactivated_ = true;
-    GetPrivacyHubNotificationController()->ShowSensorDisabledNotification(
+    GetPrivacyHubNotificationController()->ShowSoftwareSwitchNotification(
         SensorDisabledNotificationDelegate::Sensor::kCamera);
   } else {
-    GetPrivacyHubNotificationController()->UpdateSensorDisabledNotification(
+    GetPrivacyHubNotificationController()->UpdateSoftwareSwitchNotification(
         SensorDisabledNotificationDelegate::Sensor::kCamera);
   }
 }
diff --git a/ash/system/privacy_hub/camera_privacy_switch_controller.h b/ash/system/privacy_hub/camera_privacy_switch_controller.h
index 1824fba..681f210 100644
--- a/ash/system/privacy_hub/camera_privacy_switch_controller.h
+++ b/ash/system/privacy_hub/camera_privacy_switch_controller.h
@@ -17,10 +17,6 @@
 
 namespace ash {
 
-// The ID for a notification shown when the user tries to use a camera while the
-// camera is disabled in Privacy Hub.
-inline constexpr char kPrivacyHubCameraOffNotificationId[] =
-    "ash.media.privacy_hub.activity_with_disabled_camera";
 // The ID for a notification shown when the user enables camera via a HW switch
 // but it is still disabled in PrivacyHub.
 inline constexpr char
diff --git a/ash/system/privacy_hub/camera_privacy_switch_controller_unittest.cc b/ash/system/privacy_hub/camera_privacy_switch_controller_unittest.cc
index 2ef9bbcd..e584f805 100644
--- a/ash/system/privacy_hub/camera_privacy_switch_controller_unittest.cc
+++ b/ash/system/privacy_hub/camera_privacy_switch_controller_unittest.cc
@@ -14,6 +14,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/privacy_hub/privacy_hub_controller.h"
 #include "ash/system/privacy_hub/privacy_hub_metrics.h"
+#include "ash/system/privacy_hub/privacy_hub_notification_controller.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
@@ -362,12 +363,14 @@
   message_center::MessageCenter* const message_center =
       message_center::MessageCenter::Get();
   ASSERT_TRUE(message_center);
-  ASSERT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  ASSERT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 
   // An application starts accessing the camera.
   controller_->ActiveApplicationsChanged(/*application_added=*/true);
   // A notification should be fired.
-  EXPECT_TRUE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_TRUE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
   EXPECT_FALSE(GetUserPref());
 
   EXPECT_EQ(histogram_tester_.GetBucketCount(
@@ -376,10 +379,11 @@
                 true),
             0);
   // Enabling camera via clicking on the button should clear the notification
-  message_center->ClickOnNotificationButton(kPrivacyHubCameraOffNotificationId,
-                                            0);
+  message_center->ClickOnNotificationButton(
+      PrivacyHubNotificationController::kCombinedNotificationId, 0);
   EXPECT_TRUE(GetUserPref());
-  EXPECT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
   EXPECT_EQ(histogram_tester_.GetBucketCount(
                 privacy_hub_metrics::
                     kPrivacyHubCameraEnabledFromNotificationHistogram,
@@ -394,21 +398,25 @@
   message_center::MessageCenter* const message_center =
       message_center::MessageCenter::Get();
   ASSERT_TRUE(message_center);
-  ASSERT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  ASSERT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 
   // An application starts accessing the camera.
   controller_->ActiveApplicationsChanged(/*application_added=*/true);
   // A notification should be fired.
-  EXPECT_TRUE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_TRUE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
   EXPECT_FALSE(GetUserPref());
 
   // Enabling camera via clicking on the body should open the privacy hub
   // settings page.
-  message_center->ClickOnNotification(kPrivacyHubCameraOffNotificationId);
+  message_center->ClickOnNotification(
+      PrivacyHubNotificationController::kCombinedNotificationId);
 
   // The user pref should not be changed.
   EXPECT_FALSE(GetUserPref());
-  EXPECT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 
   SetUserPref(true);
 
@@ -440,19 +448,23 @@
 TEST_F(PrivacyHubCameraControllerTests,
        CameraOffNotificationRemoveViaUserPref) {
   SetUserPref(false);
-  ASSERT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  ASSERT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 
   // An application starts accessing the camera.
   controller_->ActiveApplicationsChanged(/*application_added=*/true);
   // A notification should be fired.
-  EXPECT_TRUE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_TRUE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
   EXPECT_FALSE(GetUserPref());
 
   // Enabling camera via the user pref should clear the notification
   SetUserPref(true);
   EXPECT_TRUE(GetUserPref());
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
-  EXPECT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  WaitUntilNotificationRemoved(
+      PrivacyHubNotificationController::kCombinedNotificationId);
+  EXPECT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 }
 
 TEST_F(PrivacyHubCameraControllerTests, InSessionSwitchNotification) {
@@ -460,7 +472,8 @@
   message_center::MessageCenter* const message_center =
       message_center::MessageCenter::Get();
   ASSERT_TRUE(message_center);
-  message_center->RemoveNotification(kPrivacyHubCameraOffNotificationId, false);
+  message_center->RemoveNotification(
+      PrivacyHubNotificationController::kCombinedNotificationId, false);
 
   // An application starts accessing the camera.
   controller_->ActiveApplicationsChanged(/*application_added=*/true);
@@ -468,7 +481,8 @@
   SetUserPref(false);
 
   // A notification should be fired.
-  EXPECT_TRUE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_TRUE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
   EXPECT_FALSE(GetUserPref());
 
   EXPECT_EQ(histogram_tester_.GetBucketCount(
@@ -477,10 +491,11 @@
                 true),
             0);
   // Enabling camera via clicking on the button should clear the notification
-  message_center->ClickOnNotificationButton(kPrivacyHubCameraOffNotificationId,
-                                            0);
+  message_center->ClickOnNotificationButton(
+      PrivacyHubNotificationController::kCombinedNotificationId, 0);
   EXPECT_TRUE(GetUserPref());
-  EXPECT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
   EXPECT_EQ(histogram_tester_.GetBucketCount(
                 privacy_hub_metrics::
                     kPrivacyHubCameraEnabledFromNotificationHistogram,
@@ -488,14 +503,15 @@
             1);
 }
 
-// Tests if the notification `kPrivacyHubCameraOffNotificationId` is removed
-// when the number of apps accessing the camera becomes 0.
+// Tests if camera software switch notification is removed when the number of
+// apps accessing the camera becomes 0.
 TEST_F(PrivacyHubCameraControllerTests,
        NotificationRemovedWhenNoActiveApplication) {
   SetUserPref(true);
 
   // The notification should not be in the message center initially.
-  EXPECT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 
   // This is the effect of an application starting to access the camera.
   controller_->ActiveApplicationsChanged(/*application_added=*/true);
@@ -503,32 +519,38 @@
   // Disabling camera using the software switch.
   SetUserPref(false);
 
-  // Notification `kPrivacyHubCameraOffNotificationId` should pop up.
-  EXPECT_TRUE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  // Notification `PrivacyHubNotificationController::kCombinedNotificationId`
+  // should pop up.
+  EXPECT_TRUE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 
   // The only active application stops accessing the camera the camera.
   controller_->ActiveApplicationsChanged(/*application_added=*/false);
 
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
+  WaitUntilNotificationRemoved(
+      PrivacyHubNotificationController::kCombinedNotificationId);
 
-  // Existing notification `kPrivacyHubCameraOffNotificationId` should be
+  // Existing notification
+  // `PrivacyHubNotificationController::kCombinedNotificationId` should be
   // removed as the number of active applications is 0 now.
-  EXPECT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 }
 
 // Tests if the camera software switch notification contains proper text.
 TEST_F(PrivacyHubCameraControllerTests, NotificationText) {
   // Disabling camera using the software switch.
   SetUserPref(false);
-  EXPECT_FALSE(FindNotificationById(kPrivacyHubCameraOffNotificationId));
+  EXPECT_FALSE(FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId));
 
   // Launch app1 that's accessing camera, a notification should be displayed
   // with the application name in the notification body.
   const std::u16string app1 = u"app1";
   LaunchAppAccessingCamera(app1);
 
-  message_center::Notification* notification =
-      FindNotificationById(kPrivacyHubCameraOffNotificationId);
+  message_center::Notification* notification = FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId);
   ASSERT_TRUE(notification);
   EXPECT_EQ(
       l10n_util::GetStringUTF16(IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_TITLE),
@@ -545,7 +567,8 @@
   const std::u16string app2 = u"app2";
   LaunchAppAccessingCamera(app2);
 
-  notification = FindNotificationById(kPrivacyHubCameraOffNotificationId);
+  notification = FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId);
   ASSERT_TRUE(notification);
   EXPECT_EQ(
       l10n_util::GetStringFUTF16(
@@ -558,7 +581,8 @@
   const std::u16string app3 = u"app3";
   LaunchAppAccessingCamera(app3);
 
-  notification = FindNotificationById(kPrivacyHubCameraOffNotificationId);
+  notification = FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId);
   ASSERT_TRUE(notification);
   EXPECT_EQ(l10n_util::GetStringUTF16(
                 IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_MESSAGE),
@@ -568,7 +592,8 @@
   // contain the name of the two remaining applications.
   CloseAppAccessingCamera(app2);
 
-  notification = FindNotificationById(kPrivacyHubCameraOffNotificationId);
+  notification = FindNotificationById(
+      PrivacyHubNotificationController::kCombinedNotificationId);
   ASSERT_TRUE(notification);
   EXPECT_EQ(
       l10n_util::GetStringFUTF16(
diff --git a/ash/system/privacy_hub/microphone_privacy_switch_controller.cc b/ash/system/privacy_hub/microphone_privacy_switch_controller.cc
index 6867172..fb938a1 100644
--- a/ash/system/privacy_hub/microphone_privacy_switch_controller.cc
+++ b/ash/system/privacy_hub/microphone_privacy_switch_controller.cc
@@ -47,23 +47,7 @@
     : input_stream_count_(CountActiveInputStreams()),
       mic_mute_on_(CrasAudioHandler::Get()->IsInputMuted()),
       mic_muted_by_mute_switch_(
-          CrasAudioHandler::Get()->input_muted_by_microphone_mute_switch()),
-      mute_switch_notification_(
-          kNotificationId,
-          NotificationCatalogName::kMicrophoneMute,
-          PrivacyHubNotificationDescriptor{
-              SensorDisabledNotificationDelegate::SensorSet{
-                  SensorDisabledNotificationDelegate::Sensor::kMicrophone},
-              IDS_MICROPHONE_MUTED_BY_HW_SWITCH_NOTIFICATION_TITLE,
-              IDS_ASH_LEARN_MORE,
-              std::vector<int>{
-                  IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE,
-                  IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
-                  IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES},
-              base::MakeRefCounted<
-                  PrivacyHubNotificationClickDelegate>(base::BindRepeating(
-                  PrivacyHubNotificationController::OpenSupportUrl,
-                  SensorDisabledNotificationDelegate::Sensor::kMicrophone))}) {
+          CrasAudioHandler::Get()->input_muted_by_microphone_mute_switch()) {
   Shell::Get()->session_controller()->AddObserver(this);
   CrasAudioHandler::Get()->AddAudioObserver(this);
 }
@@ -168,35 +152,37 @@
 
 void MicrophonePrivacySwitchController::SetMicrophoneNotificationVisible(
     const bool visible) {
-  mute_switch_notification_.Hide();
-
-  if (mic_muted_by_mute_switch_ && visible) {
-    mute_switch_notification_.Show();
-    return;
-  }
-
   PrivacyHubNotificationController* const privacy_hub_notification_controller =
       Shell::Get()->system_notification_controller()->privacy_hub();
+
   if (visible) {
-    privacy_hub_notification_controller->ShowSensorDisabledNotification(
-        SensorDisabledNotificationDelegate::Sensor::kMicrophone);
+    if (mic_muted_by_mute_switch_) {
+      privacy_hub_notification_controller->ShowHardwareSwitchNotification(
+          SensorDisabledNotificationDelegate::Sensor::kMicrophone);
+    } else {
+      privacy_hub_notification_controller->ShowSoftwareSwitchNotification(
+          SensorDisabledNotificationDelegate::Sensor::kMicrophone);
+    }
+
   } else {
-    privacy_hub_notification_controller->RemoveSensorDisabledNotification(
+    privacy_hub_notification_controller->RemoveSoftwareSwitchNotification(
+        SensorDisabledNotificationDelegate::Sensor::kMicrophone);
+    privacy_hub_notification_controller->RemoveHardwareSwitchNotification(
         SensorDisabledNotificationDelegate::Sensor::kMicrophone);
   }
 }
 
 void MicrophonePrivacySwitchController::UpdateMicrophoneNotification() {
-  if (mic_muted_by_mute_switch_) {
-    mute_switch_notification_.Update();
-    return;
-  }
+  PrivacyHubNotificationController* const privacy_hub_notification_controller =
+      Shell::Get()->system_notification_controller()->privacy_hub();
 
-  Shell::Get()
-      ->system_notification_controller()
-      ->privacy_hub()
-      ->UpdateSensorDisabledNotification(
-          SensorDisabledNotificationDelegate::Sensor::kMicrophone);
+  if (mic_muted_by_mute_switch_) {
+    privacy_hub_notification_controller->UpdateHardwareSwitchNotification(
+        SensorDisabledNotificationDelegate::Sensor::kMicrophone);
+  } else {
+    privacy_hub_notification_controller->UpdateSoftwareSwitchNotification(
+        SensorDisabledNotificationDelegate::Sensor::kMicrophone);
+  }
 }
 
 }  // namespace ash
diff --git a/ash/system/privacy_hub/microphone_privacy_switch_controller.h b/ash/system/privacy_hub/microphone_privacy_switch_controller.h
index 863b581..2956063 100644
--- a/ash/system/privacy_hub/microphone_privacy_switch_controller.h
+++ b/ash/system/privacy_hub/microphone_privacy_switch_controller.h
@@ -9,7 +9,6 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/session/session_observer.h"
-#include "ash/system/privacy_hub/privacy_hub_notification.h"
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "components/prefs/pref_change_registrar.h"
 
@@ -21,8 +20,6 @@
     : public CrasAudioHandler::AudioObserver,
       public SessionObserver {
  public:
-  static constexpr char kNotificationId[] = "ash://microphone_mute";
-
   MicrophonePrivacySwitchController();
   MicrophonePrivacySwitchController(const MicrophonePrivacySwitchController&) =
       delete;
@@ -60,7 +57,6 @@
   size_t input_stream_count_ = 0;
   bool mic_mute_on_ = false;
   bool mic_muted_by_mute_switch_ = false;
-  PrivacyHubNotification mute_switch_notification_;
   std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
 };
 
diff --git a/ash/system/privacy_hub/microphone_privacy_switch_controller_unittest.cc b/ash/system/privacy_hub/microphone_privacy_switch_controller_unittest.cc
index fc1e556..80fea77 100644
--- a/ash/system/privacy_hub/microphone_privacy_switch_controller_unittest.cc
+++ b/ash/system/privacy_hub/microphone_privacy_switch_controller_unittest.cc
@@ -19,6 +19,7 @@
 #include "ash/system/privacy_hub/microphone_privacy_switch_controller.h"
 #include "ash/system/privacy_hub/privacy_hub_controller.h"
 #include "ash/system/privacy_hub/privacy_hub_metrics.h"
+#include "ash/system/privacy_hub/privacy_hub_notification_controller.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
@@ -30,6 +31,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/message_center/message_center.h"
+#include "ui/message_center/message_center_observer.h"
 #include "ui/message_center/public/cpp/notification.h"
 
 namespace ash {
@@ -93,6 +95,31 @@
   MOCK_METHOD(void, MicrophoneHardwareToggleChanged, (bool), (override));
 };
 
+class RemoveNotificationWaiter : public message_center::MessageCenterObserver {
+ public:
+  explicit RemoveNotificationWaiter(const std::string& notification_id)
+      : notification_id_(notification_id) {
+    message_center::MessageCenter::Get()->AddObserver(this);
+  }
+  ~RemoveNotificationWaiter() override {
+    message_center::MessageCenter::Get()->RemoveObserver(this);
+  }
+
+  void Wait() { run_loop_.Run(); }
+
+  // message_center::MessageCenterObserver:
+  void OnNotificationRemoved(const std::string& notification_id,
+                             const bool by_user) override {
+    if (notification_id == notification_id_) {
+      run_loop_.Quit();
+    }
+  }
+
+ private:
+  const std::string notification_id_;
+  base::RunLoop run_loop_;
+};
+
 }  // namespace
 
 class PrivacyHubMicrophoneControllerTest : public AshTestBase {
@@ -135,30 +162,44 @@
         ->GetBoolean(prefs::kUserMicrophoneAllowed);
   }
 
-  message_center::Notification* GetNotification() {
+  bool IsAnyMicNotificationVisible() {
+    return GetSWSwitchNotification() != nullptr ||
+           GetHWSwitchNotification() != nullptr;
+  }
+
+  message_center::Notification* GetSWSwitchNotification() {
     return message_center::MessageCenter::Get()->FindNotificationById(
-        MicrophonePrivacySwitchController::kNotificationId);
+        PrivacyHubNotificationController::kCombinedNotificationId);
   }
 
-  message_center::Notification* GetPopupNotification() {
+  message_center::Notification* GetHWSwitchNotification() {
+    return message_center::MessageCenter::Get()->FindNotificationById(
+        PrivacyHubNotificationController::
+            kMicrophoneHardwareSwitchNotificationId);
+  }
+
+  message_center::Notification* GetSWSwitchPopupNotification() {
     return message_center::MessageCenter::Get()->FindPopupNotificationById(
-        MicrophonePrivacySwitchController::kNotificationId);
+        PrivacyHubNotificationController::kCombinedNotificationId);
   }
 
-  void MarkPopupAsShown() {
-    message_center::MessageCenter::Get()->MarkSinglePopupAsShown(
-        MicrophonePrivacySwitchController::kNotificationId, true);
+  message_center::Notification* GetHWSwitchPopupNotification() {
+    return message_center::MessageCenter::Get()->FindPopupNotificationById(
+        PrivacyHubNotificationController::
+            kMicrophoneHardwareSwitchNotificationId);
   }
 
-  void ClickOnNotificationButton() {
+  void MarkPopupAsShown(const std::string& id) {
+    message_center::MessageCenter::Get()->MarkSinglePopupAsShown(id, true);
+  }
+
+  void ClickOnNotificationButton(const std::string& id) {
     message_center::MessageCenter::Get()->ClickOnNotificationButton(
-        MicrophonePrivacySwitchController::kNotificationId,
-        /*button_index=*/0);
+        id, /*button_index=*/0);
   }
 
-  void ClickOnNotificationBody() {
-    message_center::MessageCenter::Get()->ClickOnNotification(
-        MicrophonePrivacySwitchController::kNotificationId);
+  void ClickOnNotificationBody(const std::string& id) {
+    message_center::MessageCenter::Get()->ClickOnNotification(id);
   }
 
   void SetMicrophoneMuteSwitchState(bool muted) {
@@ -175,8 +216,9 @@
         false, CrasAudioHandler::InputMuteChangeMethod::kOther);
   }
 
-  void WaitUntilNotificationRemoved() {
-    task_environment()->FastForwardBy(PrivacyHubNotification::kMinShowTime);
+  void WaitUntilNotificationRemoved(const std::string& notification_id) {
+    RemoveNotificationWaiter notification_waiter(notification_id);
+    notification_waiter.Wait();
   }
 
   void LaunchApp(absl::optional<std::u16string> app_name) {
@@ -247,57 +289,66 @@
 
 TEST_F(PrivacyHubMicrophoneControllerTest, SimpleMuteUnMute) {
   // No notification initially.
-  EXPECT_FALSE(GetNotification());
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Or when we mute.
   MuteMicrophone();
-  EXPECT_FALSE(GetNotification());
+
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Or when we unmute.
   UnMuteMicrophone();
-  EXPECT_FALSE(GetNotification());
+
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest, LaunchAppUsingMicrophone) {
   // No notification initially.
-  EXPECT_FALSE(GetNotification());
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // No notification when we unmute.
   UnMuteMicrophone();
-  EXPECT_FALSE(GetNotification());
+
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Mute the mic, still no notification.
   MuteMicrophone();
-  EXPECT_FALSE(GetNotification());
+
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Launch an app that's using the mic. The microphone mute notification should
   // show as a popup.
   LaunchApp(u"junior");
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
   // Notification should not be pinned.
-  EXPECT_FALSE(GetNotification()->rich_notification_data().pinned);
+  EXPECT_FALSE(GetSWSwitchNotification()->rich_notification_data().pinned);
 
   // Unmute again, notification goes down.
   UnMuteMicrophone();
-  WaitUntilNotificationRemoved();
-  EXPECT_FALSE(GetNotification());
+
+  WaitUntilNotificationRemoved(
+      PrivacyHubNotificationController::kCombinedNotificationId);
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest,
        SilentNotificationOnMuteWhileMicInUse) {
   // No notification initially.
-  EXPECT_FALSE(GetNotification());
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Launch an app that's using the mic, no notification because the microphone
   // is not muted.
   LaunchApp(u"junior");
-  EXPECT_FALSE(GetNotification());
+
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Mute the mic, a notification should be shown and also popup.
   MuteMicrophone();
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest,
@@ -306,18 +357,19 @@
   MuteMicrophone();
   LaunchApp(u"junior");
 
-  ASSERT_TRUE(GetNotification());
-  ASSERT_TRUE(GetPopupNotification());
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
 
   // Mark the notification as read.
-  MarkPopupAsShown();
-  ASSERT_FALSE(GetPopupNotification());
+  MarkPopupAsShown(PrivacyHubNotificationController::kCombinedNotificationId);
+
+  EXPECT_FALSE(GetSWSwitchPopupNotification());
 
   // Add an app, and verify the notification popup gets shown.
   LaunchApp(u"rose");
 
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest, RemovingStreamDoesNotShowPopup) {
@@ -326,25 +378,27 @@
   LaunchApp(u"junior");
   LaunchApp(u"rose");
 
-  ASSERT_TRUE(GetNotification());
-  ASSERT_TRUE(GetPopupNotification());
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
 
   // Mark the notification as read.
-  MarkPopupAsShown();
-  ASSERT_FALSE(GetPopupNotification());
+  MarkPopupAsShown(PrivacyHubNotificationController::kCombinedNotificationId);
+
+  ASSERT_FALSE(GetSWSwitchPopupNotification());
 
   // Close an active app, and verify that the notification popup is not
   // reshown.
   CloseApp(u"rose");
 
-  EXPECT_TRUE(GetNotification());
-  EXPECT_FALSE(GetPopupNotification());
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_FALSE(GetSWSwitchPopupNotification());
 
   // The notification should be removed if all apps are closed.
   CloseApp(u"junior");
-  WaitUntilNotificationRemoved();
 
-  EXPECT_FALSE(GetNotification());
+  WaitUntilNotificationRemoved(
+      PrivacyHubNotificationController::kCombinedNotificationId);
+  EXPECT_FALSE(GetSWSwitchNotification());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest, SwMuteNotificationActionButton) {
@@ -352,15 +406,16 @@
   LaunchApp(u"junior");
 
   // The mute notification should have an action button.
-  message_center::Notification* notification = GetNotification();
+  message_center::Notification* notification = GetSWSwitchNotification();
   ASSERT_TRUE(notification);
   EXPECT_EQ(1u, notification->buttons().size());
 
   // Clicking the action button should unmute device.
-  ClickOnNotificationButton();
-  EXPECT_FALSE(CrasAudioHandler::Get()->IsInputMuted());
+  ClickOnNotificationButton(
+      PrivacyHubNotificationController::kCombinedNotificationId);
 
-  EXPECT_FALSE(GetNotification());
+  EXPECT_FALSE(CrasAudioHandler::Get()->IsInputMuted());
+  EXPECT_FALSE(GetSWSwitchNotification());
   EXPECT_EQ(histogram_tester().GetBucketCount(
                 privacy_hub_metrics::
                     kPrivacyHubMicrophoneEnabledFromNotificationHistogram,
@@ -373,15 +428,16 @@
   LaunchApp(u"junior");
 
   // The mute notification should have an action button.
-  message_center::Notification* notification = GetNotification();
+  message_center::Notification* notification = GetSWSwitchNotification();
   ASSERT_TRUE(notification);
   EXPECT_EQ(1u, notification->buttons().size());
 
   // Clicking the action button should unmute device.
-  ClickOnNotificationBody();
-  EXPECT_TRUE(CrasAudioHandler::Get()->IsInputMuted());
+  ClickOnNotificationBody(
+      PrivacyHubNotificationController::kCombinedNotificationId);
 
-  EXPECT_FALSE(GetNotification());
+  EXPECT_TRUE(CrasAudioHandler::Get()->IsInputMuted());
+  EXPECT_FALSE(GetSWSwitchNotification());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest, HwMuteNotificationActionButton) {
@@ -389,36 +445,41 @@
 
   LaunchApp(u"junior");
 
-  // The mute notification should have a "Learn more" button.
-  message_center::Notification* notification = GetNotification();
+  // The hardware switch notification should be displayed. The notification
+  // should have a "Learn more" button.
+  EXPECT_FALSE(GetSWSwitchNotification());
+  message_center::Notification* notification = GetHWSwitchNotification();
   ASSERT_TRUE(notification);
   EXPECT_EQ(1u, notification->buttons().size());
 
   // Clicking the "Learn more" button should open a new Chrome tab with the
   // support link.
   EXPECT_CALL(new_window_delegate(), OpenUrl).Times(1);
-  ClickOnNotificationButton();
+  ClickOnNotificationButton(PrivacyHubNotificationController::
+                                kMicrophoneHardwareSwitchNotificationId);
 
   EXPECT_TRUE(CrasAudioHandler::Get()->IsInputMuted());
 
   SetMicrophoneMuteSwitchState(/*muted=*/false);
+
   ASSERT_FALSE(CrasAudioHandler::Get()->IsInputMuted());
-  EXPECT_FALSE(GetNotification());
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest, HwMuteNotificationActionBody) {
   SetMicrophoneMuteSwitchState(/*muted=*/true);
   LaunchApp(u"junior");
 
-  message_center::Notification* notification = GetNotification();
+  message_center::Notification* notification = GetHWSwitchNotification();
   ASSERT_TRUE(notification);
-  EXPECT_EQ(1u, notification->buttons().size());
 
-  ClickOnNotificationBody();
-
-  // Check that clicking the body has no effect and notification disappears.
+  // Check that clicking the body has no effect but notification disappears.
   EXPECT_TRUE(CrasAudioHandler::Get()->IsInputMuted());
-  EXPECT_FALSE(GetNotification());
+  ClickOnNotificationBody(PrivacyHubNotificationController::
+                              kMicrophoneHardwareSwitchNotificationId);
+
+  EXPECT_TRUE(CrasAudioHandler::Get()->IsInputMuted());
+  EXPECT_FALSE(GetHWSwitchNotification());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest,
@@ -428,7 +489,7 @@
   LaunchApp(u"junior");
 
   // The mute notification should have an action button.
-  message_center::Notification* notification = GetNotification();
+  message_center::Notification* notification = GetSWSwitchNotification();
   ASSERT_TRUE(notification);
   EXPECT_EQ(1u, notification->buttons().size());
   EXPECT_EQ(l10n_util::GetStringUTF16(
@@ -438,7 +499,9 @@
   // Toggle microphone mute switch and verify that new notification appears with
   // a "Learn more" button.
   SetMicrophoneMuteSwitchState(/*muted=*/true);
-  notification = GetNotification();
+
+  EXPECT_FALSE(GetSWSwitchNotification());
+  notification = GetHWSwitchNotification();
   ASSERT_TRUE(notification);
   EXPECT_EQ(1u, notification->buttons().size());
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE),
@@ -446,31 +509,42 @@
 
   SetMicrophoneMuteSwitchState(/*muted=*/false);
   ASSERT_FALSE(CrasAudioHandler::Get()->IsInputMuted());
-  WaitUntilNotificationRemoved();
-  EXPECT_FALSE(GetNotification());
+
+  WaitUntilNotificationRemoved(PrivacyHubNotificationController::
+                                   kMicrophoneHardwareSwitchNotificationId);
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest,
-       TogglingMuteSwitchDoesNotHideNotificationPopup) {
+       TogglingMuteSwitchCreatesHWPopupAndRemovesSWPopup) {
   // Mute microphone, and activate an audio input stream.
   MuteMicrophone();
-
   LaunchApp(u"junior");
 
   // Verify the notification popup is shown.
-  ASSERT_TRUE(GetNotification());
-  ASSERT_TRUE(GetPopupNotification());
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
 
-  // Toggle microphone mute switch and verify that toggling mute switch alone
-  // does not hide the notification popup.
+  // Toggle microphone mute switch and verify that toggling mute switch creates
+  // new hardware switch pop up notification and the software switch
+  // notification is removed.
   SetMicrophoneMuteSwitchState(/*muted=*/true);
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
 
+  EXPECT_TRUE(GetHWSwitchNotification());
+  EXPECT_TRUE(GetHWSwitchNotification());
+  // The software switch notification is instantly hidden.
+  EXPECT_FALSE(GetSWSwitchNotification());
+
+  // Toggling the mute switch again should remove all microphone mute
+  // notifications.
   SetMicrophoneMuteSwitchState(/*muted=*/false);
+
   ASSERT_FALSE(CrasAudioHandler::Get()->IsInputMuted());
-  WaitUntilNotificationRemoved();
-  EXPECT_FALSE(GetNotification());
+  WaitUntilNotificationRemoved(PrivacyHubNotificationController::
+                                   kMicrophoneHardwareSwitchNotificationId);
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
+  EXPECT_FALSE(GetSWSwitchNotification());
+  EXPECT_FALSE(GetHWSwitchNotification());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest,
@@ -478,13 +552,14 @@
   SetMicrophoneMuteSwitchState(/*muted=*/true);
   LaunchApp(u"junior");
 
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+  EXPECT_TRUE(GetHWSwitchNotification());
+  EXPECT_TRUE(GetHWSwitchPopupNotification());
 
   CloseApp(u"junior");
-  WaitUntilNotificationRemoved();
 
-  EXPECT_FALSE(GetNotification());
+  WaitUntilNotificationRemoved(PrivacyHubNotificationController::
+                                   kMicrophoneHardwareSwitchNotificationId);
+  EXPECT_FALSE(GetHWSwitchNotification());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest,
@@ -494,111 +569,125 @@
   SetMicrophoneMuteSwitchState(/*muted=*/true);
 
   // Notification should be shown and also popup.
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+  EXPECT_TRUE(GetHWSwitchNotification());
+  EXPECT_TRUE(GetHWSwitchPopupNotification());
 
   // Add another audio input stream, and verify the notification popup shows.
   LaunchApp(u"junior1");
 
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+  EXPECT_TRUE(GetHWSwitchNotification());
+  EXPECT_TRUE(GetHWSwitchPopupNotification());
 
   // Mark notification as read, and then remove an audio input stream.
-  MarkPopupAsShown();
-  ASSERT_FALSE(GetPopupNotification());
+  MarkPopupAsShown(PrivacyHubNotificationController::
+                       kMicrophoneHardwareSwitchNotificationId);
+
+  ASSERT_FALSE(GetHWSwitchPopupNotification());
+
   CloseApp(u"junior1");
 
   // Verify that notification popup is not reshown.
-  EXPECT_TRUE(GetNotification());
-  EXPECT_FALSE(GetPopupNotification());
+  EXPECT_TRUE(GetHWSwitchNotification());
+  EXPECT_FALSE(GetHWSwitchPopupNotification());
 
   // Adding another stream shows a popup again.
   LaunchApp(u"rose");
 
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+  EXPECT_TRUE(GetHWSwitchNotification());
+  EXPECT_TRUE(GetHWSwitchPopupNotification());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest, NotificationText) {
   // No notification initially.
-  EXPECT_FALSE(GetNotification());
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Mute the mic using sw switch, still no notification.
   MuteMicrophone();
-  EXPECT_FALSE(GetNotification());
+
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Launch an app that's using the mic, but the name of the app can not be
   // determined.
   LaunchApp(absl::nullopt);
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
   EXPECT_EQ(l10n_util::GetStringUTF16(
                 IDS_MICROPHONE_MUTED_BY_SW_SWITCH_NOTIFICATION_TITLE),
-            GetNotification()->title());
+            GetSWSwitchNotification()->title());
   // The notification body should not contain any app name.
   EXPECT_EQ(
       l10n_util::GetStringUTF16(IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE),
-      GetNotification()->message());
+      GetSWSwitchNotification()->message());
 
   // Launch an app that's using the mic, the name of the app can be determined.
   LaunchApp(u"app1");
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
   // The notification body should contain name of the app.
   EXPECT_EQ(
       l10n_util::GetStringFUTF16(
           IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME, u"app1"),
-      GetNotification()->message());
+      GetSWSwitchNotification()->message());
 
   // Launch another app that's using the mic, the name of the app can be
   // determined.
   LaunchApp(u"app2");
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
   // The notification body should contain the two available app names in the
   // order of most recently launched.
   EXPECT_EQ(l10n_util::GetStringFUTF16(
                 IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES,
                 u"app2", u"app1"),
-            GetNotification()->message());
+            GetSWSwitchNotification()->message());
 
   // Launch yet another app that's using the mic, the name of the app can be
   // determined.
   LaunchApp(u"app3");
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
+
+  EXPECT_TRUE(GetSWSwitchNotification());
+  EXPECT_TRUE(GetSWSwitchPopupNotification());
   // As more that two apps are attempting to use the microphone, we fall back to
   // displaying the generic message in the notification.
   EXPECT_EQ(
       l10n_util::GetStringUTF16(IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE),
-      GetNotification()->message());
+      GetSWSwitchNotification()->message());
 
   EXPECT_FALSE(
       ui::MicrophoneMuteSwitchMonitor::Get()->microphone_mute_switch_on());
+  EXPECT_FALSE(GetHWSwitchNotification());
+
   // Toggle the hw switch.
   SetMicrophoneMuteSwitchState(/*muted=*/true);
-  EXPECT_TRUE(GetNotification());
-  EXPECT_TRUE(GetPopupNotification());
-  // The title of the notification should be different when microphone is muted
-  // by the hw switch.
+
+  EXPECT_TRUE(GetHWSwitchNotification());
+  EXPECT_TRUE(GetHWSwitchPopupNotification());
   EXPECT_EQ(l10n_util::GetStringUTF16(
                 IDS_MICROPHONE_MUTED_BY_HW_SWITCH_NOTIFICATION_TITLE),
-            GetNotification()->title());
+            GetHWSwitchNotification()->title());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE),
+      GetHWSwitchNotification()->message());
 }
 
 TEST_F(PrivacyHubMicrophoneControllerTest, NotificationUpdatedWhenAppClosed) {
   // No notification initially.
-  EXPECT_FALSE(GetNotification());
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Mute the mic using sw switch, still no notification.
   MuteMicrophone();
-  EXPECT_FALSE(GetNotification());
+
+  EXPECT_FALSE(IsAnyMicNotificationVisible());
 
   // Launch app1 that's accessing the mic, a notification should be displayed
   // with the application name in the notification body.
   const std::u16string app1 = u"app1";
   LaunchApp(app1);
-  message_center::Notification* notification_ptr = GetNotification();
+
+  message_center::Notification* notification_ptr = GetSWSwitchNotification();
   ASSERT_TRUE(notification_ptr);
   EXPECT_EQ(
       l10n_util::GetStringFUTF16(
@@ -610,7 +699,8 @@
   // notification body.
   const std::u16string app2 = u"app2";
   LaunchApp(app2);
-  notification_ptr = GetNotification();
+
+  notification_ptr = GetSWSwitchNotification();
   ASSERT_TRUE(notification_ptr);
   EXPECT_EQ(l10n_util::GetStringFUTF16(
                 IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES,
@@ -620,7 +710,8 @@
   // Close one of the applications. The notification message should be updated
   // to only contain the name of the other application.
   CloseApp(app1);
-  notification_ptr = GetNotification();
+
+  notification_ptr = GetSWSwitchNotification();
   ASSERT_TRUE(notification_ptr);
   EXPECT_EQ(
       l10n_util::GetStringFUTF16(
@@ -630,10 +721,10 @@
   // Test the HW switch notification case.
   // HW switch is turned ON.
   SetMicrophoneMuteSwitchState(/*muted=*/true);
-
   // Launch the closed app (app1) again.
   LaunchApp(app1);
-  notification_ptr = GetNotification();
+
+  notification_ptr = GetHWSwitchNotification();
   ASSERT_TRUE(notification_ptr);
   EXPECT_EQ(l10n_util::GetStringFUTF16(
                 IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES,
@@ -643,7 +734,8 @@
   // Closing one of the applications should remove the name of that application
   // from the hw switch notification message.
   CloseApp(app2);
-  notification_ptr = GetNotification();
+
+  notification_ptr = GetHWSwitchNotification();
   ASSERT_TRUE(notification_ptr);
   EXPECT_EQ(
       l10n_util::GetStringFUTF16(
diff --git a/ash/system/privacy_hub/privacy_hub_notification.cc b/ash/system/privacy_hub/privacy_hub_notification.cc
index aedc762..e5d821f 100644
--- a/ash/system/privacy_hub/privacy_hub_notification.cc
+++ b/ash/system/privacy_hub/privacy_hub_notification.cc
@@ -29,6 +29,22 @@
 
 namespace ash {
 
+bool operator<(const PrivacyHubNotificationDescriptor& descriptor1,
+               const PrivacyHubNotificationDescriptor& descriptor2) {
+  return descriptor1.sensors().ToEnumBitmask() <
+         descriptor2.sensors().ToEnumBitmask();
+}
+
+bool operator<(const PrivacyHubNotificationDescriptor& descriptor,
+               const uint64_t& sensors_bitmask) {
+  return descriptor.sensors().ToEnumBitmask() < sensors_bitmask;
+}
+
+bool operator<(const uint64_t& sensors_bitmask,
+               const PrivacyHubNotificationDescriptor& descriptor) {
+  return sensors_bitmask < descriptor.sensors().ToEnumBitmask();
+}
+
 PrivacyHubNotificationClickDelegate::PrivacyHubNotificationClickDelegate(
     base::RepeatingClosure button_click) {
   button_callbacks_[0] = std::move(button_click);
@@ -71,11 +87,11 @@
 PrivacyHubNotificationDescriptor::PrivacyHubNotificationDescriptor(
     const SensorDisabledNotificationDelegate::SensorSet& sensors,
     const int title_id,
-    const int button_id,
+    const std::vector<int>& button_ids,
     const std::vector<int>& message_ids,
     const scoped_refptr<PrivacyHubNotificationClickDelegate> delegate)
     : title_id_(title_id),
-      button_id_(button_id),
+      button_ids_(button_ids),
       sensors_(sensors),
       message_ids_(message_ids),
       delegate_(delegate) {
@@ -83,6 +99,8 @@
   DCHECK(delegate);
   DCHECK(message_ids.size() < 2u || !sensors.Empty())
       << "Specify at least one sensor when providing more than one message ID";
+  DCHECK_LE(button_ids.size(), 2u) << "Privacy hub notifications are not "
+                                      "supposed to have more than two buttons.";
 }
 
 PrivacyHubNotificationDescriptor::PrivacyHubNotificationDescriptor(
@@ -97,20 +115,28 @@
     const std::string& id,
     const ash::NotificationCatalogName catalog_name,
     const PrivacyHubNotificationDescriptor& descriptor)
-    : id_(id),
-      message_ids_(descriptor.message_ids()),
-      sensors_(descriptor.sensors()),
-      delegate_(descriptor.delegate()),
-      button_text_(l10n_util::GetStringUTF16(descriptor.button_id_)) {
+    : id_(id), sensors_(descriptor.sensors()), catalog_name_(catalog_name) {
+  notification_descriptors_.emplace(descriptor);
+  SetNotificationContent();
+
   builder_.SetId(id)
       .SetCatalogName(catalog_name)
-      .SetDelegate(descriptor.delegate())
-      .SetTitleId(descriptor.title_id_)
-      .SetOptionalFields(MakeOptionalFields())
       .SetSmallImage(vector_icons::kSettingsIcon)
       .SetWarningLevel(message_center::SystemNotificationWarningLevel::NORMAL);
 }
 
+PrivacyHubNotification::PrivacyHubNotification(
+    const std::string& id,
+    const ash::NotificationCatalogName catalog_name,
+    const std::vector<PrivacyHubNotificationDescriptor>& descriptors)
+    : PrivacyHubNotification(id, catalog_name, descriptors.at(0)) {
+  DCHECK_GT(descriptors.size(), 1u);
+
+  for (unsigned int i = 1; i < descriptors.size(); ++i) {
+    notification_descriptors_.emplace(descriptors.at(i));
+  }
+}
+
 PrivacyHubNotification::~PrivacyHubNotification() = default;
 
 void PrivacyHubNotification::Show() {
@@ -132,7 +158,16 @@
   last_time_shown_ = base::Time::Now();
 }
 
-void PrivacyHubNotification::Hide() {
+void PrivacyHubNotification::Hide(const bool ignore_delay) {
+  if (ignore_delay) {
+    if (remove_timer_.IsRunning()) {
+      remove_timer_.Stop();
+    }
+    RemoveNotification(id_);
+    last_time_shown_.reset();
+    return;
+  }
+
   if (!last_time_shown_) {
     return;
   }
@@ -157,16 +192,20 @@
   }
 }
 
-void PrivacyHubNotification::SetSecondButton(base::RepeatingClosure callback,
-                                             int title_id) {
-  message_center::RichNotificationData optional_fields = MakeOptionalFields();
-  optional_fields.buttons.emplace_back(l10n_util::GetStringUTF16(title_id));
-  builder_.SetOptionalFields(optional_fields);
-  delegate_->SetSecondButtonCallback(std::move(callback));
+void PrivacyHubNotification::SetSensors(
+    const SensorDisabledNotificationDelegate::SensorSet sensors) {
+  DCHECK_GT(notification_descriptors_.size(), 1u)
+      << "`sensors_` should only be updated when multiple notification "
+         "descriptors are provided.";
+
+  if (sensors_ != sensors) {
+    sensors_ = sensors;
+    has_sensors_changed_ = true;
+  }
 }
 
-std::vector<std::u16string> PrivacyHubNotification::GetAppsAccessingSensors()
-    const {
+std::vector<std::u16string> PrivacyHubNotification::GetAppsAccessingSensors(
+    const size_t number_of_apps) const {
   std::vector<std::u16string> app_names;
 
   if (SensorDisabledNotificationDelegate* delegate =
@@ -177,7 +216,7 @@
         if (!base::Contains(app_names, app)) {
           app_names.push_back(app);
         }
-        if (app_names.size() == message_ids_.size()) {
+        if (app_names.size() == number_of_apps) {
           return app_names;
         }
       }
@@ -188,22 +227,41 @@
 }
 
 void PrivacyHubNotification::SetNotificationContent() {
-  const std::vector<std::u16string> apps = GetAppsAccessingSensors();
+  auto descriptor = notification_descriptors_.find(sensors_.ToEnumBitmask());
+  DCHECK(descriptor != notification_descriptors_.end());
 
-  if (const size_t num_apps = apps.size(); num_apps < message_ids_.size()) {
-    builder_.SetMessageWithArgs(message_ids_.at(num_apps), apps);
-  } else {
-    builder_.SetMessageId(message_ids_.at(0));
+  if (has_sensors_changed_) {
+    message_center::RichNotificationData optional_fields;
+    optional_fields.remove_on_click = true;
+
+    for (int button_id : descriptor->button_ids()) {
+      optional_fields.buttons.emplace_back(
+          l10n_util::GetStringUTF16(button_id));
+    }
+
+    builder_.SetDelegate(descriptor->delegate())
+        .SetOptionalFields(optional_fields);
+
+    if (catalog_name_ != NotificationCatalogName::kCameraPrivacySwitch) {
+      builder_.SetTitleId(descriptor->title_id_);
+    }
+
+    has_sensors_changed_ = false;
   }
-}
 
-message_center::RichNotificationData
-PrivacyHubNotification::MakeOptionalFields() const {
-  message_center::RichNotificationData optional_fields;
-  optional_fields.remove_on_click = true;
-  optional_fields.buttons.emplace_back(button_text_);
+  if (catalog_name_ == NotificationCatalogName::kCameraPrivacySwitch) {
+    return;
+  }
 
-  return optional_fields;
+  const std::vector<std::u16string> apps =
+      GetAppsAccessingSensors(descriptor->message_ids().size());
+
+  if (const size_t num_apps = apps.size();
+      num_apps < descriptor->message_ids().size()) {
+    builder_.SetMessageWithArgs(descriptor->message_ids().at(num_apps), apps);
+  } else {
+    builder_.SetMessageId(descriptor->message_ids().at(0));
+  }
 }
 
 }  // namespace ash
diff --git a/ash/system/privacy_hub/privacy_hub_notification.h b/ash/system/privacy_hub/privacy_hub_notification.h
index 6612971..d0e904a 100644
--- a/ash/system/privacy_hub/privacy_hub_notification.h
+++ b/ash/system/privacy_hub/privacy_hub_notification.h
@@ -5,6 +5,7 @@
 #ifndef ASH_SYSTEM_PRIVACY_HUB_PRIVACY_HUB_NOTIFICATION_H_
 #define ASH_SYSTEM_PRIVACY_HUB_PRIVACY_HUB_NOTIFICATION_H_
 
+#include <set>
 #include <string>
 #include <vector>
 
@@ -61,7 +62,7 @@
   PrivacyHubNotificationDescriptor(
       const SensorDisabledNotificationDelegate::SensorSet& sensors,
       int title_id,
-      int button_id,
+      const std::vector<int>& button_ids,
       const std::vector<int>& message_ids,
       scoped_refptr<PrivacyHubNotificationClickDelegate> delegate);
   PrivacyHubNotificationDescriptor(
@@ -70,6 +71,8 @@
       const PrivacyHubNotificationDescriptor& other);
   ~PrivacyHubNotificationDescriptor();
 
+  const std::vector<int>& button_ids() const { return button_ids_; }
+
   const SensorDisabledNotificationDelegate::SensorSet& sensors() const {
     return sensors_;
   }
@@ -81,9 +84,9 @@
   }
 
   int title_id_;
-  int button_id_;
 
  private:
+  std::vector<int> button_ids_;
   SensorDisabledNotificationDelegate::SensorSet sensors_;
   std::vector<int> message_ids_;
   scoped_refptr<PrivacyHubNotificationClickDelegate> delegate_;
@@ -98,28 +101,50 @@
 
   // Create a new notification.
   // When calling `Show() or `Update()`:
-  // If `sensors_` is empty, the generic notification message will be displayed.
+  // If `sensors_` is empty, the generic notification message from `descriptor`
+  // will be displayed.
   // If `sensors_` is non-empty and `n` applications are using the sensors in
   // `sensors_`, the displayed notification message will contain `n` application
-  // names. If a notification message with `n` application names is not
-  // provided, the generic notification message will be displayed.
+  // names. If `descriptor` does not contain a notification message with `n`
+  // application names, the generic notification message from `descriptor` will
+  // be displayed.
   PrivacyHubNotification(const std::string& id,
                          NotificationCatalogName catalog_name,
                          const PrivacyHubNotificationDescriptor& descriptor);
+
+  // When PrivacyHubNotification is constructed with multiple
+  // `PrivacyHubNotificationDescriptor`s, which descriptor to use will be
+  // decided depending on the value of `sensors_`. When `sensors_` changes, the
+  // descriptor to use will also change.
+  //`descriptors` must have multiple `PrivacyHubNotificationDescriptor` objects,
+  // use the previous constructor otherwise please.
+  PrivacyHubNotification(
+      const std::string& id,
+      NotificationCatalogName catalog_name,
+      const std::vector<PrivacyHubNotificationDescriptor>& descriptors);
+
   PrivacyHubNotification(PrivacyHubNotification&&) = delete;
   PrivacyHubNotification& operator=(PrivacyHubNotification&&) = delete;
+
   ~PrivacyHubNotification();
 
   // Show the notification to the user for at least `kMinShowTime`. Every time
   // `Show()` is called, the notification will pop up. For silent updates, use
-  // the `Update()` function. Calls to `Hide()` are delayed until `kMinShowTime`
-  // time has passed and the notification is hidden then.
+  // the `Update()` function.
   void Show();
 
-  // Hide the notification from the user if it has already been shown for at
-  // least `kMinShowTime`. If not the notification will be shown for the
-  // remaining time and then hidden.
-  void Hide();
+  // Hide the notification from the user. Calls to `Hide()` are delayed until
+  // `kMinShowTime` time has passed since the time notification was last
+  // displayed.
+  // When `ignore_delay` is true, the notification is instantly hidden from the
+  // message center. `ignore_delay` should always be false except for some
+  // special cases.
+  // For example, microphone software switch notification and hardware switch
+  // notification are represented by different notification objects and the
+  // notifications represent the same information except the action buttons. In
+  // such cases, displaying both of the simultaneously may seem redundant. This
+  // is a perfect example to use `Hide()` with `ignore_delay = true`.
+  void Hide(bool ignore_delay = false);
 
   // Silently updates the notification when needed, for example, when an
   // application stops accessing a sensor and the name of that application needs
@@ -127,10 +152,8 @@
   // again.
   void Update();
 
-  // Add an additional button to the notification. The button title will be
-  // generated from the `title_id`. Clicking the button will invoke the
-  // `callback`. Only one additional button can be active at the same time.
-  void SetSecondButton(base::RepeatingClosure callback, int title_id);
+  // Updates the value of `sensors_`.
+  void SetSensors(SensorDisabledNotificationDelegate::SensorSet sensors);
 
   // Get the underlying `SystemNotificationBuilder` to do modifications beyond
   // what this wrapper allows you to do. If you change the ID of the message
@@ -138,26 +161,48 @@
   SystemNotificationBuilder& builder() { return builder_; }
 
  private:
-  // Get names of apps accessing sensors in `sensors_`. At most
-  // `message_ids_.size()` elements will be returned.
-  std::vector<std::u16string> GetAppsAccessingSensors() const;
+  // Get names of apps accessing sensors in `sensors_`. At most `number_of_apps`
+  // elements will be returned.
+  std::vector<std::u16string> GetAppsAccessingSensors(
+      size_t number_of_apps) const;
 
-  // Sets the content(message, title, buttons etc.) of the notification
-  // depending on the values of `sensors_` and `message_ids_`.
+  // Propagates information about the update in notification content (message,
+  // title, buttons etc.) to the underlying `SystemNotificationBuilder`. This is
+  // always done before showing or updating a notification.
   void SetNotificationContent();
 
-  // Create an object of optional data fields with the defaults applying to
-  // every Privacy Hub notification.
-  message_center::RichNotificationData MakeOptionalFields() const;
-
   std::string id_;
-  SystemNotificationBuilder builder_;
-  std::vector<int> message_ids_;
+
+  // A set of `PrivacyHubNotificationDescriptor`s. Appropriate
+  // `PrivacyHubNotificationDescriptor` for a specific `SensorSet` can be found
+  // using the standard `find` function. `sensors_.ToEnumBitmask()` can be used
+  // as the key for the `find` function.
+  std::set<PrivacyHubNotificationDescriptor, std::less<>>
+      notification_descriptors_;
+
   SensorDisabledNotificationDelegate::SensorSet sensors_;
+
+  // `notification_descriptors_` is a set of
+  // `PrivacyHubNotificationDescriptor`s. The content in the descriptors are
+  // used to update the underlying `SystemNotificationBuilder`. Before a call to
+  // `Show()` or `Update()`, the underlying builder needs to be updated. Content
+  // of which descriptor to use to update the builder depends on the value
+  // current value of `sensors_` enumset.
+  // `has_sensors_changed_` being true means that `sensors_` was updated but the
+  // underlying builder was not updated after that.
+  bool has_sensors_changed_ = true;
+
+  SystemNotificationBuilder builder_;
+
   absl::optional<base::Time> last_time_shown_;
   base::OneShotTimer remove_timer_;
-  scoped_refptr<PrivacyHubNotificationClickDelegate> delegate_;
-  std::u16string button_text_;
+
+  // TODO(b/271809217): Refactor camera HW switch notification implementation
+  // Notification for the camera hardware switch is currently using only a
+  // subset of `PrivacyHubNotification` properties. `catalog_name_` is stored to
+  // determine if the notification is for the camera hardware switch to handle
+  // it specially.
+  NotificationCatalogName catalog_name_;
 };
 
 }  // namespace ash
diff --git a/ash/system/privacy_hub/privacy_hub_notification_controller.cc b/ash/system/privacy_hub/privacy_hub_notification_controller.cc
index d36fa4c..6f33200 100644
--- a/ash/system/privacy_hub/privacy_hub_notification_controller.cc
+++ b/ash/system/privacy_hub/privacy_hub_notification_controller.cc
@@ -14,6 +14,7 @@
 #include "ash/system/privacy_hub/microphone_privacy_switch_controller.h"
 #include "ash/system/privacy_hub/privacy_hub_metrics.h"
 #include "ash/system/privacy_hub/privacy_hub_notification.h"
+#include "base/notreached.h"
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "ui/message_center/message_center.h"
 
@@ -29,45 +30,38 @@
 constexpr char kLearnMoreUrl[] =
     "https://support.google.com/chromebook/?p=privacy_hub";
 
+void LogInvalidSensor(const SensorDisabledNotificationDelegate::Sensor sensor) {
+  NOTREACHED() << "Invalid sensor: "
+               << static_cast<std::underlying_type_t<
+                      SensorDisabledNotificationDelegate::Sensor>>(sensor);
+}
+
 }  // namespace
 
 PrivacyHubNotificationController::PrivacyHubNotificationController() {
-  sw_notifications_.emplace(
-      Sensor::kCamera,
-      std::make_unique<PrivacyHubNotification>(
-          kPrivacyHubCameraOffNotificationId,
-          NotificationCatalogName::kPrivacyHubCamera,
-          PrivacyHubNotificationDescriptor{
-              SensorDisabledNotificationDelegate::SensorSet{Sensor::kCamera},
-              IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_TITLE,
-              IDS_PRIVACY_HUB_TURN_ON_CAMERA_ACTION_BUTTON,
-              std::vector<int>{
-                  IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_MESSAGE,
-                  IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
-                  IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES},
-              base::MakeRefCounted<PrivacyHubNotificationClickDelegate>(
-                  base::BindRepeating([]() {
-                    CameraPrivacySwitchController::
-                        SetAndLogCameraPreferenceFromNotification(true);
-                  }))}));
+  auto camera_notification_descriptor = PrivacyHubNotificationDescriptor(
+      SensorSet{Sensor::kCamera}, IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_TITLE,
+      std::vector<int>{IDS_PRIVACY_HUB_TURN_ON_CAMERA_ACTION_BUTTON},
+      std::vector<int>{
+          IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_MESSAGE,
+          IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
+          IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES},
+      base::MakeRefCounted<PrivacyHubNotificationClickDelegate>(
+          base::BindRepeating([]() {
+            CameraPrivacySwitchController::
+                SetAndLogCameraPreferenceFromNotification(true);
+          })));
 
-  sw_notifications_.emplace(
-      Sensor::kMicrophone,
-      std::make_unique<PrivacyHubNotification>(
-          MicrophonePrivacySwitchController::kNotificationId,
-          NotificationCatalogName::kMicrophoneMute,
-          PrivacyHubNotificationDescriptor{
-              SensorDisabledNotificationDelegate::SensorSet{
-                  Sensor::kMicrophone},
-              IDS_MICROPHONE_MUTED_BY_SW_SWITCH_NOTIFICATION_TITLE,
-              IDS_MICROPHONE_MUTED_NOTIFICATION_ACTION_BUTTON,
-              std::vector<int>{
-                  IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE,
-                  IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
-                  IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES},
-              base::MakeRefCounted<PrivacyHubNotificationClickDelegate>(
-                  base::BindRepeating(
-                      []() { SetAndLogMicrophoneMute(false); }))}));
+  auto microphone_notification_descriptor = PrivacyHubNotificationDescriptor(
+      SensorSet{Sensor::kMicrophone},
+      IDS_MICROPHONE_MUTED_BY_SW_SWITCH_NOTIFICATION_TITLE,
+      std::vector<int>{IDS_MICROPHONE_MUTED_NOTIFICATION_ACTION_BUTTON},
+      std::vector<int>{
+          IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE,
+          IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
+          IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES},
+      base::MakeRefCounted<PrivacyHubNotificationClickDelegate>(
+          base::BindRepeating([]() { SetAndLogMicrophoneMute(false); })));
 
   auto combined_delegate = base::MakeRefCounted<
       PrivacyHubNotificationClickDelegate>(base::BindRepeating([]() {
@@ -75,53 +69,155 @@
     CameraPrivacySwitchController::SetAndLogCameraPreferenceFromNotification(
         true);
   }));
-  combined_delegate->SetMessageClickCallback(base::BindRepeating(
-      &PrivacyHubNotificationController::HandleNotificationMessageClicked,
-      weak_ptr_factory_.GetWeakPtr()));
+
+  combined_delegate->SetSecondButtonCallback(base::BindRepeating(
+      &PrivacyHubNotificationController::OpenPrivacyHubSettingsPage));
+
+  auto combined_notification_descriptor = PrivacyHubNotificationDescriptor(
+      SensorSet{Sensor::kCamera, Sensor::kMicrophone},
+      IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_TITLE,
+      std::vector<int>{
+          IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_BUTTON},
+      std::vector<int>{
+          IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_MESSAGE,
+          IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
+          IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES},
+      combined_delegate);
 
   combined_notification_ = std::make_unique<PrivacyHubNotification>(
       kCombinedNotificationId, NotificationCatalogName::kPrivacyHubMicAndCamera,
-      PrivacyHubNotificationDescriptor{
-          SensorDisabledNotificationDelegate::SensorSet{Sensor::kCamera,
-                                                        Sensor::kMicrophone},
-          IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_TITLE,
-          IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_BUTTON,
-          std::vector<int>{
-              IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_MESSAGE,
-              IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
-              IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES},
-          combined_delegate});
+      std::vector<PrivacyHubNotificationDescriptor>{
+          camera_notification_descriptor, microphone_notification_descriptor,
+          combined_notification_descriptor});
 
-  combined_notification_->SetSecondButton(
-      base::BindRepeating(
-          &PrivacyHubNotificationController::OpenPrivacyHubSettingsPage),
-      IDS_PRIVACY_HUB_OPEN_SETTINGS_PAGE_BUTTON);
+  microphone_hw_switch_notification_ = std::make_unique<PrivacyHubNotification>(
+      kMicrophoneHardwareSwitchNotificationId,
+      NotificationCatalogName::kMicrophoneMute,
+      PrivacyHubNotificationDescriptor{
+          SensorDisabledNotificationDelegate::SensorSet{Sensor::kMicrophone},
+          IDS_MICROPHONE_MUTED_BY_HW_SWITCH_NOTIFICATION_TITLE,
+          std::vector<int>{IDS_ASH_LEARN_MORE},
+          std::vector<int>{
+              IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE,
+              IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
+              IDS_MICROPHONE_MUTED_NOTIFICATION_MESSAGE_WITH_TWO_APP_NAMES},
+          base::MakeRefCounted<PrivacyHubNotificationClickDelegate>(
+              base::BindRepeating(
+                  PrivacyHubNotificationController::OpenSupportUrl,
+                  SensorDisabledNotificationDelegate::Sensor::kMicrophone))});
 }
 
 PrivacyHubNotificationController::~PrivacyHubNotificationController() = default;
 
-void PrivacyHubNotificationController::ShowSensorDisabledNotification(
+void PrivacyHubNotificationController::ShowSoftwareSwitchNotification(
     const Sensor sensor) {
-  sensors_.Put(sensor);
-
-  ShowAllActiveNotifications(sensor);
-}
-
-void PrivacyHubNotificationController::RemoveSensorDisabledNotification(
-    const Sensor sensor) {
-  sensors_.Remove(sensor);
-
-  if (!sensors_.HasAny(combinable_sensors_)) {
-    ignore_new_combinable_notifications_ = false;
+  switch (sensor) {
+    case Sensor::kMicrophone: {
+      // Microphone software switch notification will be displayed now. If the
+      // hardware switch notification is still not cleared, let's force clear
+      // it.
+      microphone_hw_switch_notification_->Hide(/*ignore_delay=*/true);
+      [[fallthrough]];
+    }
+    case Sensor::kCamera: {
+      AddSensor(sensor);
+      combined_notification_->Show();
+      break;
+    }
+    default: {
+      LogInvalidSensor(sensor);
+      break;
+    }
   }
-
-  ShowAllActiveNotifications(sensor);
 }
 
-void PrivacyHubNotificationController::UpdateSensorDisabledNotification(
+void PrivacyHubNotificationController::RemoveSoftwareSwitchNotification(
     const Sensor sensor) {
-  sw_notifications_.at(sensor)->Update();
-  combined_notification_->Update();
+  switch (sensor) {
+    case Sensor::kCamera: {
+      [[fallthrough]];
+    }
+    case Sensor::kMicrophone: {
+      RemoveSensor(sensor);
+      if (!sensors_.Empty()) {
+        combined_notification_->Update();
+      } else {
+        combined_notification_->Hide();
+      }
+      break;
+    }
+    default: {
+      LogInvalidSensor(sensor);
+      break;
+    }
+  }
+}
+
+void PrivacyHubNotificationController::UpdateSoftwareSwitchNotification(
+    const Sensor sensor) {
+  switch (sensor) {
+    case Sensor::kCamera: {
+      [[fallthrough]];
+    }
+    case Sensor::kMicrophone: {
+      combined_notification_->Update();
+      break;
+    }
+    default: {
+      LogInvalidSensor(sensor);
+      break;
+    }
+  }
+}
+
+void PrivacyHubNotificationController::ShowHardwareSwitchNotification(
+    const Sensor sensor) {
+  switch (sensor) {
+    case Sensor::kMicrophone: {
+      RemoveSensor(sensor);
+      if (!sensors_.Empty()) {
+        combined_notification_->Update();
+      } else {
+        // As the hardware switch notification for microphone will be displayed
+        // now, let's force remove the sw switch notification.
+        combined_notification_->Hide(/*ignore_delay=*/true);
+      }
+      microphone_hw_switch_notification_->Show();
+      break;
+    }
+    default: {
+      LogInvalidSensor(sensor);
+      break;
+    }
+  }
+}
+
+void PrivacyHubNotificationController::RemoveHardwareSwitchNotification(
+    const Sensor sensor) {
+  switch (sensor) {
+    case Sensor::kMicrophone: {
+      microphone_hw_switch_notification_->Hide();
+      break;
+    }
+    default: {
+      LogInvalidSensor(sensor);
+      break;
+    }
+  }
+}
+
+void PrivacyHubNotificationController::UpdateHardwareSwitchNotification(
+    const Sensor sensor) {
+  switch (sensor) {
+    case Sensor::kMicrophone: {
+      microphone_hw_switch_notification_->Update();
+      break;
+    }
+    default: {
+      LogInvalidSensor(sensor);
+      break;
+    }
+  }
 }
 
 void PrivacyHubNotificationController::OpenPrivacyHubSettingsPage() {
@@ -148,43 +244,14 @@
       NewWindowDelegate::Disposition::kNewForegroundTab);
 }
 
-void PrivacyHubNotificationController::ShowAllActiveNotifications(
-    const Sensor changed_sensor) {
-  message_center::MessageCenter* message_center =
-      message_center::MessageCenter::Get();
-  DCHECK(message_center);
-
-  if (combinable_sensors_.Has(changed_sensor)) {
-    combined_notification_->Hide();
-
-    if (ignore_new_combinable_notifications_)
-      return;
-
-    if (sensors_.HasAll(combinable_sensors_)) {
-      for (Sensor sensor : combinable_sensors_) {
-        sw_notifications_.at(sensor)->Hide();
-      }
-
-      combined_notification_->Show();
-
-      return;
-    }
-  }
-
-  // Remove the notification for the current sensor in case the sensor is
-  // no longer active it won't be shown again in the for loop later.
-  // The other case where the sensor is added (again) to the set this
-  // (re)surfaces the notification, e.g. because a different app now wants to
-  // access the sensor.
-  sw_notifications_.at(changed_sensor)->Hide();
-
-  for (const Sensor active_sensor : sensors_) {
-    sw_notifications_.at(active_sensor)->Show();
-  }
+void PrivacyHubNotificationController::AddSensor(Sensor sensor) {
+  sensors_.Put(sensor);
+  combined_notification_->SetSensors(sensors_);
 }
 
-void PrivacyHubNotificationController::HandleNotificationMessageClicked() {
-  ignore_new_combinable_notifications_ = true;
+void PrivacyHubNotificationController::RemoveSensor(Sensor sensor) {
+  sensors_.Remove(sensor);
+  combined_notification_->SetSensors(sensors_);
 }
 
 }  // namespace ash
diff --git a/ash/system/privacy_hub/privacy_hub_notification_controller.h b/ash/system/privacy_hub/privacy_hub_notification_controller.h
index 09cfc8d..60adf94 100644
--- a/ash/system/privacy_hub/privacy_hub_notification_controller.h
+++ b/ash/system/privacy_hub/privacy_hub_notification_controller.h
@@ -10,8 +10,6 @@
 #include "ash/ash_export.h"
 #include "ash/public/cpp/sensor_disabled_notification_delegate.h"
 #include "ash/system/privacy_hub/privacy_hub_notification.h"
-#include "base/containers/flat_map.h"
-#include "base/memory/weak_ptr.h"
 
 namespace ash {
 
@@ -29,20 +27,41 @@
       const PrivacyHubNotificationController&) = delete;
   ~PrivacyHubNotificationController();
 
-  // Called by any sensor system when a notification for `sensor`
-  // should be shown to the user.
-  void ShowSensorDisabledNotification(Sensor sensor);
+  // Called by any sensor system when a software switch notification for
+  // `sensor` should be shown to the user.
+  void ShowSoftwareSwitchNotification(Sensor sensor);
 
-  // Called by any sensor system when a notification for `sensor`
-  // should be removed from the notification center and popups.
-  void RemoveSensorDisabledNotification(Sensor sensor);
+  // Called by any sensor system when a software switch notification for
+  // `sensor` should be removed from the notification center and popups.
+  void RemoveSoftwareSwitchNotification(Sensor sensor);
 
-  // Called by any sensor system when a notification for `sensor` should be
-  // updated, for example, when an application stops accessing `sensor`.
-  void UpdateSensorDisabledNotification(Sensor sensor);
+  // Called by any sensor system when a software switch notification for
+  // `sensor` should be updated, for example, when an application stops
+  // accessing `sensor`.
+  void UpdateSoftwareSwitchNotification(Sensor sensor);
 
+  // Called by any sensor system when a hardware switch notification for
+  // `sensor` should be shown to the user.
+  void ShowHardwareSwitchNotification(Sensor sensor);
+
+  // Called by any sensor system when a hardware switch notification for
+  // `sensor` should be removed from the notification center and popups.
+  void RemoveHardwareSwitchNotification(Sensor sensor);
+
+  // Called by any sensor system when a hardware switch notification for
+  // `sensor` should be updated, for example, when an application stops
+  // accessing `sensor`.
+  void UpdateHardwareSwitchNotification(Sensor sensor);
+
+  // This same id will be used for
+  // - microphone software switch notification
+  // - camera software switch notification
+  // - microphone and camera combined notification
   static constexpr const char kCombinedNotificationId[] =
-      "ash.system.privacy_hub.enable_microphone_and_camera";
+      "ash.system.privacy_hub.enable_microphone_and/or_camera";
+
+  static constexpr const char kMicrophoneHardwareSwitchNotificationId[] =
+      "ash://microphone_hardware_mute";
 
   // Open the Privacy Hub settings page and log that this interaction came from
   // a notification.
@@ -53,26 +72,22 @@
   static void OpenSupportUrl(Sensor sensor);
 
  private:
-  // Show all notifications that are currently active and combine them if
-  // necessary. From the `changed_sensor` in combination with `sensors_`,
-  // `combinable_sensors_` and `ignore_new_combinable_notifications_` the
-  // appropriate notification will be shown and unnecessary notifications
-  // removed if necessary.
-  void ShowAllActiveNotifications(Sensor changed_sensor);
-
-  void HandleNotificationMessageClicked();
+  void AddSensor(SensorDisabledNotificationDelegate::Sensor sensor);
+  void RemoveSensor(SensorDisabledNotificationDelegate::Sensor sensor);
 
   const SensorSet combinable_sensors_{Sensor::kMicrophone, Sensor::kCamera};
-  // Flag to keep track if the user opened the settings page and don't show
-  // them new notifications of sensors that can be combined or the combined
-  // notification until the number of active uses falls to 0.
-  bool ignore_new_combinable_notifications_{false};
+
+  // `combined_notification_` will be displayed for the sensors which are
+  // currently in `sensors_`. Only `combinable_sensors_` can be in `sensors_`.
   SensorSet sensors_;
+
+  // This PrivacyHubNotification object will be used to display
+  // - microphone software switch notification
+  // - camera software switch notification
+  // - microphone and camera combined notification
   std::unique_ptr<PrivacyHubNotification> combined_notification_;
-  base::flat_map<Sensor, std::unique_ptr<PrivacyHubNotification>>
-      sw_notifications_;
-  base::WeakPtrFactory<PrivacyHubNotificationController> weak_ptr_factory_{
-      this};
+
+  std::unique_ptr<PrivacyHubNotification> microphone_hw_switch_notification_;
 };
 
 }  // namespace ash
diff --git a/ash/system/privacy_hub/privacy_hub_notification_controller_unittest.cc b/ash/system/privacy_hub/privacy_hub_notification_controller_unittest.cc
index 1a107c0..8a0cda6 100644
--- a/ash/system/privacy_hub/privacy_hub_notification_controller_unittest.cc
+++ b/ash/system/privacy_hub/privacy_hub_notification_controller_unittest.cc
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/test/test_new_window_delegate.h"
 #include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "ash/system/privacy_hub/camera_privacy_switch_controller.h"
 #include "ash/system/privacy_hub/microphone_privacy_switch_controller.h"
 #include "ash/system/privacy_hub/privacy_hub_controller.h"
@@ -22,6 +23,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "chromeos/ash/components/dbus/audio/fake_cras_audio_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/message_center_observer.h"
 #include "ui/message_center/notification_list.h"
@@ -40,8 +42,7 @@
 
 class RemoveNotificationWaiter : public message_center::MessageCenterObserver {
  public:
-  explicit RemoveNotificationWaiter(const std::string& notification_id)
-      : notification_id_(notification_id) {
+  RemoveNotificationWaiter() {
     message_center::MessageCenter::Get()->AddObserver(this);
   }
   ~RemoveNotificationWaiter() override {
@@ -53,13 +54,13 @@
   // message_center::MessageCenterObserver:
   void OnNotificationRemoved(const std::string& notification_id,
                              const bool by_user) override {
-    if (notification_id == notification_id_) {
+    if (notification_id ==
+        PrivacyHubNotificationController::kCombinedNotificationId) {
       run_loop_.Quit();
     }
   }
 
  private:
-  const std::string notification_id_;
   base::RunLoop run_loop_;
 };
 
@@ -100,13 +101,12 @@
   void TearDown() override { AshTestBase::TearDown(); }
 
  protected:
-  const message_center::Notification* GetNotification(
-      const char* notification_id =
-          PrivacyHubNotificationController::kCombinedNotificationId) const {
+  const message_center::Notification* GetNotification() const {
     const message_center::NotificationList::Notifications& notifications =
         message_center::MessageCenter::Get()->GetVisibleNotifications();
     for (const auto* notification : notifications) {
-      if (notification->id() == notification_id) {
+      if (notification->id() ==
+          PrivacyHubNotificationController::kCombinedNotificationId) {
         return notification;
       }
     }
@@ -134,7 +134,7 @@
       FakeCrasAudioClient::Get()->SetActiveInputStreamsWithPermission(
           {{"CRAS_CLIENT_TYPE_CHROME", 1}});
     } else {
-      controller_->ShowSensorDisabledNotification(sensor);
+      controller_->ShowSoftwareSwitchNotification(sensor);
     }
   }
 
@@ -148,28 +148,26 @@
       FakeCrasAudioClient::Get()->SetActiveInputStreamsWithPermission(
           {{"CRAS_CLIENT_TYPE_CHROME", 0}});
     } else {
-      controller_->RemoveSensorDisabledNotification(sensor);
+      controller_->RemoveSoftwareSwitchNotification(sensor);
     }
   }
 
   void ShowCombinedNotification() {
     ShowNotification(Sensor::kCamera);
-    controller_->ShowSensorDisabledNotification(Sensor::kMicrophone);
+    ShowNotification(Sensor::kMicrophone);
   }
 
-  void ExpectNoNotificationActive() const {
-    EXPECT_FALSE(GetNotification());
-    EXPECT_FALSE(GetNotification(kPrivacyHubCameraOffNotificationId));
-    EXPECT_FALSE(
-        GetNotification(MicrophonePrivacySwitchController::kNotificationId));
+  void RemoveCombinedNotification() {
+    RemoveNotification(Sensor::kCamera);
+    RemoveNotification(Sensor::kMicrophone);
   }
 
   const base::HistogramTester& histogram_tester() const {
     return histogram_tester_;
   }
 
-  void WaitUntilNotificationRemoved(const std::string& notification_id) {
-    RemoveNotificationWaiter notification_waiter(notification_id);
+  void WaitUntilNotificationRemoved() {
+    RemoveNotificationWaiter notification_waiter;
     notification_waiter.Wait();
   }
 
@@ -184,84 +182,106 @@
   std::unique_ptr<ash::TestNewWindowDelegateProvider> window_delegate_provider_;
 };
 
-TEST_F(PrivacyHubNotificationControllerTest, ShowCameraNotification) {
-  ExpectNoNotificationActive();
+TEST_F(PrivacyHubNotificationControllerTest, CameraNotificationShowAndHide) {
+  EXPECT_FALSE(GetNotification());
+
   ShowNotification(Sensor::kCamera);
-  EXPECT_TRUE(GetNotification(kPrivacyHubCameraOffNotificationId));
+
+  const message_center::Notification* notification_ptr = GetNotification();
+  ASSERT_TRUE(notification_ptr);
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_TITLE),
+      notification_ptr->title());
+
+  RemoveNotification(Sensor::kCamera);
+
+  WaitUntilNotificationRemoved();
+  EXPECT_FALSE(GetNotification());
 }
 
-TEST_F(PrivacyHubNotificationControllerTest, ShowMicrophoneNotification) {
-  ExpectNoNotificationActive();
+TEST_F(PrivacyHubNotificationControllerTest,
+       MicrophoneNotificationShowAndHide) {
+  EXPECT_FALSE(GetNotification());
 
   ShowNotification(Sensor::kMicrophone);
-  EXPECT_TRUE(
-      GetNotification(MicrophonePrivacySwitchController::kNotificationId));
+
+  const message_center::Notification* notification_ptr = GetNotification();
+  ASSERT_TRUE(notification_ptr);
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_MICROPHONE_MUTED_BY_SW_SWITCH_NOTIFICATION_TITLE),
+            notification_ptr->title());
 
   RemoveNotification(Sensor::kMicrophone);
-  WaitUntilNotificationRemoved(
-      MicrophonePrivacySwitchController::kNotificationId);
-  EXPECT_FALSE(
-      GetNotification(MicrophonePrivacySwitchController::kNotificationId));
+
+  WaitUntilNotificationRemoved();
+  EXPECT_FALSE(GetNotification());
 }
 
-TEST_F(PrivacyHubNotificationControllerTest, CombinedNotificationActive) {
-  ExpectNoNotificationActive();
+TEST_F(PrivacyHubNotificationControllerTest, CombinedNotificationShowAndHide) {
+  EXPECT_FALSE(GetNotification());
 
   ShowCombinedNotification();
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
 
-  EXPECT_TRUE(GetNotification());
-  EXPECT_FALSE(GetNotification(kPrivacyHubCameraOffNotificationId));
-  EXPECT_FALSE(
-      GetNotification(MicrophonePrivacySwitchController::kNotificationId));
+  const message_center::Notification* notification_ptr = GetNotification();
+  ASSERT_TRUE(notification_ptr);
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_TITLE),
+            notification_ptr->title());
+
+  RemoveCombinedNotification();
+
+  WaitUntilNotificationRemoved();
+  EXPECT_FALSE(GetNotification());
 }
 
 TEST_F(PrivacyHubNotificationControllerTest, CombinedNotificationBuilding) {
-  ExpectNoNotificationActive();
+  EXPECT_FALSE(GetNotification());
 
   ShowNotification(Sensor::kMicrophone);
-  EXPECT_FALSE(GetNotification());
-  EXPECT_FALSE(GetNotification(kPrivacyHubCameraOffNotificationId));
-  EXPECT_TRUE(
-      GetNotification(MicrophonePrivacySwitchController::kNotificationId));
+
+  const message_center::Notification* notification_ptr = GetNotification();
+  ASSERT_TRUE(notification_ptr);
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_MICROPHONE_MUTED_BY_SW_SWITCH_NOTIFICATION_TITLE),
+            notification_ptr->title());
 
   ShowNotification(Sensor::kCamera);
-  WaitUntilNotificationRemoved(
-      MicrophonePrivacySwitchController::kNotificationId);
-  EXPECT_TRUE(GetNotification());
-  EXPECT_FALSE(GetNotification(kPrivacyHubCameraOffNotificationId));
-  EXPECT_FALSE(
-      GetNotification(MicrophonePrivacySwitchController::kNotificationId));
 
-  // Enable microphone from elsewhere.
+  notification_ptr = GetNotification();
+  ASSERT_TRUE(notification_ptr);
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_TITLE),
+            notification_ptr->title());
+
   RemoveNotification(Sensor::kMicrophone);
-  EXPECT_FALSE(GetNotification());
-  EXPECT_TRUE(GetNotification(kPrivacyHubCameraOffNotificationId));
-  EXPECT_FALSE(
-      GetNotification(MicrophonePrivacySwitchController::kNotificationId));
 
-  // Remove the camera notification as well.
+  notification_ptr = GetNotification();
+  ASSERT_TRUE(notification_ptr);
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_TITLE),
+      notification_ptr->title());
+
   RemoveNotification(Sensor::kCamera);
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
 
-  ExpectNoNotificationActive();
+  WaitUntilNotificationRemoved();
+  EXPECT_FALSE(GetNotification());
 }
 
 TEST_F(PrivacyHubNotificationControllerTest,
        CombinedNotificationClickedButOnlyOneSensorEnabledInSettings) {
-  ExpectNoNotificationActive();
+  EXPECT_FALSE(GetNotification());
 
   ShowCombinedNotification();
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
 
-  EXPECT_TRUE(GetNotification());
-  EXPECT_FALSE(GetNotification(kPrivacyHubCameraOffNotificationId));
-  EXPECT_FALSE(
-      GetNotification(MicrophonePrivacySwitchController::kNotificationId));
+  const message_center::Notification* notification_ptr = GetNotification();
+  ASSERT_TRUE(notification_ptr);
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_TITLE),
+            notification_ptr->title());
 
   ClickOnNotificationBody();
 
-  ExpectNoNotificationActive();
+  EXPECT_FALSE(GetNotification());
 
   // Go to (quick)settings and enable microphone.
   RemoveNotification(Sensor::kMicrophone);
@@ -269,111 +289,105 @@
   // Since the user clicked on the notification body they acknowledged that
   // camera is disabled as well. So don't show that notification even though
   // the sensor is still disabled.
-  ExpectNoNotificationActive();
+  EXPECT_FALSE(GetNotification());
 
   // Disable camera as well
   RemoveNotification(Sensor::kCamera);
-  ExpectNoNotificationActive();
+  EXPECT_FALSE(GetNotification());
 
   // Now that no sensor is in use anymore when accessing both again the
   // combined notification should show up again.
   ShowCombinedNotification();
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
 
-  EXPECT_TRUE(GetNotification());
-  EXPECT_FALSE(GetNotification(kPrivacyHubCameraOffNotificationId));
-  EXPECT_FALSE(
-      GetNotification(MicrophonePrivacySwitchController::kNotificationId));
+  notification_ptr = GetNotification();
+  ASSERT_TRUE(notification_ptr);
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_TITLE),
+            notification_ptr->title());
 }
 
 TEST_F(PrivacyHubNotificationControllerTest, ClickOnNotificationButton) {
-  ExpectNoNotificationActive();
-  ShowCombinedNotification();
-  EXPECT_TRUE(GetNotification());
+  EXPECT_FALSE(GetNotification());
 
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::
-                    kPrivacyHubCameraEnabledFromNotificationHistogram,
-                true),
-            0);
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::
-                    kPrivacyHubMicrophoneEnabledFromNotificationHistogram,
-                true),
-            0);
+  ShowCombinedNotification();
+
+  EXPECT_TRUE(GetNotification());
+  EXPECT_EQ(0, histogram_tester().GetBucketCount(
+                   privacy_hub_metrics::
+                       kPrivacyHubCameraEnabledFromNotificationHistogram,
+                   true));
+  EXPECT_EQ(0, histogram_tester().GetBucketCount(
+                   privacy_hub_metrics::
+                       kPrivacyHubMicrophoneEnabledFromNotificationHistogram,
+                   true));
 
   ClickOnNotificationButton();
 
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
-
-  ExpectNoNotificationActive();
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::
-                    kPrivacyHubCameraEnabledFromNotificationHistogram,
-                true),
-            1);
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::
-                    kPrivacyHubMicrophoneEnabledFromNotificationHistogram,
-                true),
-            1);
+  EXPECT_FALSE(GetNotification());
+  EXPECT_EQ(1, histogram_tester().GetBucketCount(
+                   privacy_hub_metrics::
+                       kPrivacyHubCameraEnabledFromNotificationHistogram,
+                   true));
+  EXPECT_EQ(1, histogram_tester().GetBucketCount(
+                   privacy_hub_metrics::
+                       kPrivacyHubMicrophoneEnabledFromNotificationHistogram,
+                   true));
 }
 
 TEST_F(PrivacyHubNotificationControllerTest, ClickOnSecondNotificationButton) {
-  ExpectNoNotificationActive();
+  EXPECT_FALSE(GetNotification());
+
   ShowCombinedNotification();
+
   EXPECT_TRUE(GetNotification());
 
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::kPrivacyHubOpenedHistogram,
-                privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification),
-            0);
-  EXPECT_EQ(GetSystemTrayClient()->show_os_settings_privacy_hub_count(), 0);
+  EXPECT_EQ(
+      0, histogram_tester().GetBucketCount(
+             privacy_hub_metrics::kPrivacyHubOpenedHistogram,
+             privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification));
+  EXPECT_EQ(0, GetSystemTrayClient()->show_os_settings_privacy_hub_count());
 
   ClickOnNotificationButton(1);
 
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
+  EXPECT_FALSE(GetNotification());
 
-  ExpectNoNotificationActive();
-
-  EXPECT_EQ(GetSystemTrayClient()->show_os_settings_privacy_hub_count(), 1);
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::kPrivacyHubOpenedHistogram,
-                privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification),
-            1);
+  EXPECT_EQ(1, GetSystemTrayClient()->show_os_settings_privacy_hub_count());
+  EXPECT_EQ(
+      1, histogram_tester().GetBucketCount(
+             privacy_hub_metrics::kPrivacyHubOpenedHistogram,
+             privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification));
 }
 
 TEST_F(PrivacyHubNotificationControllerTest, ClickOnNotificationBody) {
-  ExpectNoNotificationActive();
-  ShowCombinedNotification();
-  EXPECT_TRUE(GetNotification());
+  EXPECT_FALSE(GetNotification());
 
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::kPrivacyHubOpenedHistogram,
-                privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification),
-            0);
+  ShowCombinedNotification();
+
+  EXPECT_TRUE(GetNotification());
+  EXPECT_EQ(
+      0, histogram_tester().GetBucketCount(
+             privacy_hub_metrics::kPrivacyHubOpenedHistogram,
+             privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification));
 
   ClickOnNotificationBody();
 
-  WaitUntilNotificationRemoved(kPrivacyHubCameraOffNotificationId);
-
-  ExpectNoNotificationActive();
+  EXPECT_FALSE(GetNotification());
 }
 
 TEST_F(PrivacyHubNotificationControllerTest, OpenPrivacyHubSettingsPage) {
-  EXPECT_EQ(GetSystemTrayClient()->show_os_settings_privacy_hub_count(), 0);
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::kPrivacyHubOpenedHistogram,
-                privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification),
-            0);
+  EXPECT_EQ(0, GetSystemTrayClient()->show_os_settings_privacy_hub_count());
+  EXPECT_EQ(
+      0, histogram_tester().GetBucketCount(
+             privacy_hub_metrics::kPrivacyHubOpenedHistogram,
+             privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification));
 
   PrivacyHubNotificationController::OpenPrivacyHubSettingsPage();
 
-  EXPECT_EQ(GetSystemTrayClient()->show_os_settings_privacy_hub_count(), 1);
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                privacy_hub_metrics::kPrivacyHubOpenedHistogram,
-                privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification),
-            1);
+  EXPECT_EQ(1, GetSystemTrayClient()->show_os_settings_privacy_hub_count());
+  EXPECT_EQ(
+      1, histogram_tester().GetBucketCount(
+             privacy_hub_metrics::kPrivacyHubOpenedHistogram,
+             privacy_hub_metrics::PrivacyHubNavigationOrigin::kNotification));
 }
 
 TEST_F(PrivacyHubNotificationControllerTest, OpenPrivacyHubSupportPage) {
@@ -382,17 +396,17 @@
   auto test_sensor = [histogram_tester = &histogram_tester()](
                          Sensor privacy_hub_sensor,
                          PrivacyHubLearnMoreSensor lean_more_sensor) {
-    EXPECT_EQ(histogram_tester->GetBucketCount(
+    EXPECT_EQ(0,
+              histogram_tester->GetBucketCount(
                   privacy_hub_metrics::kPrivacyHubLearnMorePageOpenedHistogram,
-                  lean_more_sensor),
-              0);
+                  lean_more_sensor));
 
     PrivacyHubNotificationController::OpenSupportUrl(privacy_hub_sensor);
 
-    EXPECT_EQ(histogram_tester->GetBucketCount(
+    EXPECT_EQ(1,
+              histogram_tester->GetBucketCount(
                   privacy_hub_metrics::kPrivacyHubLearnMorePageOpenedHistogram,
-                  lean_more_sensor),
-              1);
+                  lean_more_sensor));
   };
 
   EXPECT_CALL(*new_window_delegate(), OpenUrl).Times(2);
diff --git a/ash/system/privacy_hub/privacy_hub_notification_unittest.cc b/ash/system/privacy_hub/privacy_hub_notification_unittest.cc
index 2522b0a..bc58dd80 100644
--- a/ash/system/privacy_hub/privacy_hub_notification_unittest.cc
+++ b/ash/system/privacy_hub/privacy_hub_notification_unittest.cc
@@ -114,7 +114,8 @@
                 SensorDisabledNotificationDelegate::SensorSet{
                     SensorDisabledNotificationDelegate::Sensor::kMicrophone},
                 IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_TITLE,
-                IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_BUTTON,
+                std::vector<int>{
+                    IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_BUTTON},
                 std::vector<int>{
                     IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_MESSAGE,
                     IDS_PRIVACY_HUB_MICROPHONE_AND_CAMERA_OFF_NOTIFICATION_MESSAGE_WITH_ONE_APP_NAME,
@@ -272,26 +273,6 @@
   EXPECT_FALSE(GetPopupNotification());
 }
 
-TEST_F(PrivacyHubNotificationTest, AddButton) {
-  notification().Show();
-
-  EXPECT_EQ(GetNotification()->rich_notification_data().buttons.size(), 1u);
-
-  int second_button_clicked = 0;
-  notification().SetSecondButton(
-      base::BindLambdaForTesting(
-          [&second_button_clicked]() { second_button_clicked++; }),
-      IDS_PRIVACY_HUB_OPEN_SETTINGS_PAGE_BUTTON);
-
-  notification().Update();
-  message_center::Notification* test_notification = GetNotification();
-  ASSERT_EQ(test_notification->rich_notification_data().buttons.size(), 2u);
-
-  EXPECT_EQ(second_button_clicked, 0);
-  test_notification->delegate()->Click(1, absl::nullopt);
-  EXPECT_EQ(second_button_clicked, 1);
-}
-
 TEST_F(PrivacyHubNotificationTest, WithApps) {
   // No apps -> generic notification text.
   notification().Show();
diff --git a/chrome/browser/ui/ash/media_client_impl.cc b/chrome/browser/ui/ash/media_client_impl.cc
index cc31c682..6a97d5f 100644
--- a/chrome/browser/ui/ash/media_client_impl.cc
+++ b/chrome/browser/ui/ash/media_client_impl.cc
@@ -280,7 +280,7 @@
           ash::PrivacyHubNotificationDescriptor{
               ash::SensorDisabledNotificationDelegate::SensorSet{},
               IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_TITLE,
-              IDS_ASH_LEARN_MORE,
+              std::vector<int>{IDS_ASH_LEARN_MORE},
               std::vector<int>{
                   IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_MESSAGE},
               base::MakeRefCounted<