diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 0efb848650ccb..da9dab463e80f 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -606,9 +606,15 @@ Style notes:
       <message name="IDS_ASH_STATUS_TRAY_CAST_PAUSE" desc="Label for a button in the cast notification to pause the current cast mirroring session to a Chromecast device">
         Pause
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_CAST_PAUSE_CASTING" desc="Label for a button in the system tray to pause the current cast mirroring session to a Chromecast device">
+        Pause casting
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_CAST_RESUME" desc="Label for a button in the cast notification to resume a paused cast mirroring session to a Chromecast device">
         Resume
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_CAST_RESUME_CASTING" desc="Label for a button in the system tray to resume a paused cast mirroring session to a Chromecast device">
+        Resume casting
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_QUIET_MODE_TOOLTIP" desc="The tooltip text for the status area icon to tell do-not-disturb mode is currently on.">
         Do Not Disturb is on
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_PAUSE_CASTING.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_PAUSE_CASTING.png.sha1
new file mode 100644
index 0000000000000..87510852cd70c
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_PAUSE_CASTING.png.sha1
@@ -0,0 +1 @@
+c8647e60121c1b5a16205154492ba70f83d50d29
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_RESUME_CASTING.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_RESUME_CASTING.png.sha1
new file mode 100644
index 0000000000000..fd0de7fe1c3b4
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_RESUME_CASTING.png.sha1
@@ -0,0 +1 @@
+12ca3db87ee67fdfa25fd132901a7f491ef74f29
\ No newline at end of file
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index 1861d26fda605..996d54d316d24 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -269,6 +269,8 @@ aggregate_vector_icons("ash_vector_icons") {
     "quick_settings_a11y_sticky_keys.icon",
     "quick_settings_cast.icon",
     "quick_settings_cast_connected.icon",
+    "quick_settings_circle_pause.icon",
+    "quick_settings_circle_play.icon",
     "quick_settings_circle_stop.icon",
     "quick_settings_left_arrow.icon",
     "quick_settings_managed.icon",
diff --git a/ash/resources/vector_icons/quick_settings_circle_pause.icon b/ash/resources/vector_icons/quick_settings_circle_pause.icon
new file mode 100644
index 0000000000000..e9272e86728f1
--- /dev/null
+++ b/ash/resources/vector_icons/quick_settings_circle_pause.icon
@@ -0,0 +1,45 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 7.5f, 13,
+H_LINE_TO, 9,
+V_LINE_TO, 7,
+H_LINE_TO, 7.5f,
+V_LINE_TO, 13,
+CLOSE,
+MOVE_TO, 11, 13,
+H_LINE_TO, 12.5f,
+V_LINE_TO, 7,
+H_LINE_TO, 11,
+V_LINE_TO, 13,
+CLOSE,
+MOVE_TO, 10, 18,
+CUBIC_TO, 8.9f, 18, 7.87f, 17.79f, 6.9f, 17.38f,
+CUBIC_TO, 5.92f, 16.96f, 5.07f, 16.39f, 4.33f, 15.67f,
+CUBIC_TO, 3.61f, 14.93f, 3.04f, 14.08f, 2.63f, 13.1f,
+CUBIC_TO, 2.21f, 12.13f, 2, 11.1f, 2, 10,
+CUBIC_TO, 2, 8.89f, 2.21f, 7.85f, 2.63f, 6.9f,
+CUBIC_TO, 3.04f, 5.92f, 3.61f, 5.08f, 4.33f, 4.35f,
+CUBIC_TO, 5.07f, 3.62f, 5.92f, 3.04f, 6.9f, 2.63f,
+CUBIC_TO, 7.87f, 2.21f, 8.9f, 2, 10, 2,
+CUBIC_TO, 11.11f, 2, 12.15f, 2.21f, 13.1f, 2.63f,
+CUBIC_TO, 14.08f, 3.04f, 14.92f, 3.62f, 15.65f, 4.35f,
+CUBIC_TO, 16.38f, 5.08f, 16.96f, 5.92f, 17.38f, 6.9f,
+CUBIC_TO, 17.79f, 7.85f, 18, 8.89f, 18, 10,
+CUBIC_TO, 18, 11.1f, 17.79f, 12.13f, 17.38f, 13.1f,
+CUBIC_TO, 16.96f, 14.08f, 16.38f, 14.93f, 15.65f, 15.67f,
+CUBIC_TO, 14.92f, 16.39f, 14.08f, 16.96f, 13.1f, 17.38f,
+CUBIC_TO, 12.15f, 17.79f, 11.11f, 18, 10, 18,
+CLOSE,
+MOVE_TO, 10, 16.5f,
+CUBIC_TO, 11.81f, 16.5f, 13.34f, 15.87f, 14.6f, 14.6f,
+CUBIC_TO, 15.87f, 13.34f, 16.5f, 11.81f, 16.5f, 10,
+CUBIC_TO, 16.5f, 8.19f, 15.87f, 6.66f, 14.6f, 5.4f,
+CUBIC_TO, 13.34f, 4.13f, 11.81f, 3.5f, 10, 3.5f,
+CUBIC_TO, 8.19f, 3.5f, 6.66f, 4.13f, 5.4f, 5.4f,
+CUBIC_TO, 4.13f, 6.66f, 3.5f, 8.19f, 3.5f, 10,
+CUBIC_TO, 3.5f, 11.81f, 4.13f, 13.34f, 5.4f, 14.6f,
+CUBIC_TO, 6.66f, 15.87f, 8.19f, 16.5f, 10, 16.5f,
+CLOSE
\ No newline at end of file
diff --git a/ash/resources/vector_icons/quick_settings_circle_play.icon b/ash/resources/vector_icons/quick_settings_circle_play.icon
new file mode 100644
index 0000000000000..aefbbc6bc4f92
--- /dev/null
+++ b/ash/resources/vector_icons/quick_settings_circle_play.icon
@@ -0,0 +1,38 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 8, 13.5f,
+LINE_TO, 13.5f, 10,
+LINE_TO, 8, 6.5f,
+V_LINE_TO, 13.5f,
+CLOSE,
+MOVE_TO, 10, 18,
+CUBIC_TO, 8.9f, 18, 7.87f, 17.79f, 6.9f, 17.38f,
+CUBIC_TO, 5.92f, 16.96f, 5.07f, 16.39f, 4.33f, 15.67f,
+CUBIC_TO, 3.61f, 14.93f, 3.04f, 14.08f, 2.63f, 13.1f,
+CUBIC_TO, 2.21f, 12.13f, 2, 11.1f, 2, 10,
+CUBIC_TO, 2, 8.89f, 2.21f, 7.85f, 2.63f, 6.9f,
+CUBIC_TO, 3.04f, 5.92f, 3.61f, 5.08f, 4.33f, 4.35f,
+CUBIC_TO, 5.07f, 3.62f, 5.92f, 3.04f, 6.9f, 2.63f,
+CUBIC_TO, 7.87f, 2.21f, 8.9f, 2, 10, 2,
+CUBIC_TO, 11.11f, 2, 12.15f, 2.21f, 13.1f, 2.63f,
+CUBIC_TO, 14.08f, 3.04f, 14.92f, 3.62f, 15.65f, 4.35f,
+CUBIC_TO, 16.38f, 5.08f, 16.96f, 5.92f, 17.38f, 6.9f,
+CUBIC_TO, 17.79f, 7.85f, 18, 8.89f, 18, 10,
+CUBIC_TO, 18, 11.1f, 17.79f, 12.13f, 17.38f, 13.1f,
+CUBIC_TO, 16.96f, 14.08f, 16.38f, 14.93f, 15.65f, 15.67f,
+CUBIC_TO, 14.92f, 16.39f, 14.08f, 16.96f, 13.1f, 17.38f,
+CUBIC_TO, 12.15f, 17.79f, 11.11f, 18, 10, 18,
+CLOSE,
+MOVE_TO, 10, 16.5f,
+CUBIC_TO, 11.81f, 16.5f, 13.34f, 15.87f, 14.6f, 14.6f,
+CUBIC_TO, 15.87f, 13.34f, 16.5f, 11.81f, 16.5f, 10,
+CUBIC_TO, 16.5f, 8.19f, 15.87f, 6.66f, 14.6f, 5.4f,
+CUBIC_TO, 13.34f, 4.13f, 11.81f, 3.5f, 10, 3.5f,
+CUBIC_TO, 8.19f, 3.5f, 6.66f, 4.13f, 5.4f, 5.4f,
+CUBIC_TO, 4.13f, 6.66f, 3.5f, 8.19f, 3.5f, 10,
+CUBIC_TO, 3.5f, 11.81f, 4.13f, 13.34f, 5.4f, 14.6f,
+CUBIC_TO, 6.66f, 15.87f, 8.19f, 16.5f, 10, 16.5f,
+CLOSE
\ No newline at end of file
diff --git a/ash/system/cast/cast_detailed_view.cc b/ash/system/cast/cast_detailed_view.cc
index 577511ea630b3..aceda3c36ff92 100644
--- a/ash/system/cast/cast_detailed_view.cc
+++ b/ash/system/cast/cast_detailed_view.cc
@@ -35,11 +35,15 @@
 #include "ui/views/border.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/layout/box_layout.h"
+#include "ui/views/view_class_properties.h"
 
 namespace ash {
 
 namespace {
 
+// Extra spacing to add between cast stop buttons and the edge of the qs tray.
+constexpr int kStopButtonExtraMargin = 4;
+
 // Returns the correct vector icon for |icon_type|. Some types may be different
 // for branded builds.
 const gfx::VectorIcon& SinkIconTypeToIcon(SinkIconType icon_type) {
@@ -65,6 +69,21 @@ const gfx::VectorIcon& SinkIconTypeToIcon(SinkIconType icon_type) {
   return kSystemMenuCastGenericIcon;
 }
 
+std::unique_ptr<views::View> MakeButtonContainer() {
+  std::unique_ptr<views::View> button_container =
+      std::make_unique<views::View>();
+  views::BoxLayout* manager =
+      button_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kHorizontal));
+  manager->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
+  manager->set_between_child_spacing(kTrayPopupLabelRightPadding);
+  button_container->SetProperty(
+      views::kMarginsKey,
+      gfx::Insets::TLBR(0, 0, 0,
+                        kStopButtonExtraMargin + kQsExtraMarginsFromRightEdge));
+  return button_container;
+}
+
 }  // namespace
 
 CastDetailedView::CastDetailedView(DetailedViewDelegate* delegate)
@@ -85,40 +104,18 @@ void CastDetailedView::CreateItems() {
 
 void CastDetailedView::OnDevicesUpdated(
     const std::vector<SinkAndRoute>& sinks_routes) {
+  sinks_and_routes_.clear();
   // Add/update existing.
   for (const auto& device : sinks_routes)
     sinks_and_routes_.insert(std::make_pair(device.sink.id, device));
 
-  // Remove non-existent sinks. Removing an element invalidates all existing
-  // iterators.
-  auto iter = sinks_and_routes_.begin();
-  while (iter != sinks_and_routes_.end()) {
-    bool has_receiver = false;
-    for (auto& receiver : sinks_routes) {
-      if (iter->first == receiver.sink.id)
-        has_receiver = true;
-    }
-
-    if (has_receiver)
-      ++iter;
-    else
-      iter = sinks_and_routes_.erase(iter);
-  }
-
   // Update UI.
   UpdateReceiverListFromCachedData();
   Layout();
 }
 
 void CastDetailedView::UpdateReceiverListFromCachedData() {
-  // Remove all of the existing views.
-  view_to_sink_map_.clear();
-  scroll_content()->RemoveAllChildViews();
-  add_access_code_device_ = nullptr;
-  if (zero_state_view_) {
-    RemoveChildViewT(zero_state_view_.get());
-    zero_state_view_ = nullptr;
-  }
+  RemoveAllViews();
 
   // QsRevamp places items in a rounded container.
   const bool is_qs_revamp_enabled = features::IsQsRevampEnabled();
@@ -130,18 +127,7 @@ void CastDetailedView::UpdateReceiverListFromCachedData() {
   // Per product requirement, access code receiver should be shown before other
   // receivers.
   if (CastConfigController::Get()->AccessCodeCastingEnabled()) {
-    add_access_code_device_ = AddScrollListItem(
-        item_container, vector_icons::kKeyboardIcon,
-        l10n_util::GetStringUTF16(
-            IDS_ASH_STATUS_TRAY_CAST_ACCESS_CODE_CAST_CONNECT));
-    if (chromeos::features::IsJellyEnabled()) {
-      // `views::ImageView` does not support changing the color, so set the
-      // image with an updated `ui::ImageModel`.
-      add_access_code_device_->icon()->SetImage(ui::ImageModel::FromVectorIcon(
-          vector_icons::kKeyboardIcon, cros_tokens::kCrosSysPrimary));
-      add_access_code_device_->text_label()->SetEnabledColorId(
-          cros_tokens::kCrosSysPrimary);
-    }
+    AddAccessCodeCastButton(item_container);
   }
 
   // Add a view for each receiver.
@@ -153,20 +139,10 @@ void CastDetailedView::UpdateReceiverListFromCachedData() {
         base::UTF8ToUTF16(sink.name));
     view_to_sink_map_[container] = sink.id;
 
-    // Add a stop casting button if this machine ("local source") is casting to
-    // the device. See also CastNotificationController::OnDevicesUpdated().
+    // Add receiver action buttons if this machine ("local source") is casting
+    // to the device. See also CastNotificationController::OnDevicesUpdated().
     if (is_qs_revamp_enabled && !route.id.empty() && route.is_local_source) {
-      auto button = std::make_unique<PillButton>(
-          base::BindRepeating(&CastDetailedView::StopCasting,
-                              base::Unretained(this), route.id),
-          l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_STOP_CASTING),
-          PillButton::kDefaultWithIconLeading, &kQuickSettingsCircleStopIcon);
-      button->SetBackgroundColorId(cros_tokens::kCrosSysErrorContainer);
-      button->SetIconColorId(cros_tokens::kCrosSysError);
-      button->SetButtonTextColorId(cros_tokens::kCrosSysError);
-      container->AddRightView(
-          button.release(),
-          views::CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 0, 4)));
+      AddReceiverActionButtons(sink, route, container, item_container);
     }
   }
 
@@ -221,6 +197,102 @@ void CastDetailedView::StopCasting(const std::string& route_id) {
   CloseBubble();  // Deletes `this`.
 }
 
+void CastDetailedView::FreezePressed(const std::string& route_id,
+                                     bool is_frozen) {
+  DCHECK(features::IsQsRevampEnabled());
+  if (is_frozen) {
+    CastConfigController::Get()->UnfreezeRoute(route_id);
+  } else {
+    CastConfigController::Get()->FreezeRoute(route_id);
+    CloseBubble();
+  }
+}
+
+void CastDetailedView::RemoveAllViews() {
+  view_to_sink_map_.clear();
+  sink_extra_views_map_.clear();
+  scroll_content()->RemoveAllChildViews();
+  add_access_code_device_ = nullptr;
+  if (zero_state_view_) {
+    RemoveChildViewT(zero_state_view_.get());
+    zero_state_view_ = nullptr;
+  }
+}
+
+void CastDetailedView::AddAccessCodeCastButton(
+    views::View* receiver_list_view) {
+  add_access_code_device_ =
+      AddScrollListItem(receiver_list_view, vector_icons::kKeyboardIcon,
+                        l10n_util::GetStringUTF16(
+                            IDS_ASH_STATUS_TRAY_CAST_ACCESS_CODE_CAST_CONNECT));
+  if (chromeos::features::IsJellyEnabled()) {
+    // `views::ImageView` does not support changing the color, so set the
+    // image with an updated `ui::ImageModel`.
+    add_access_code_device_->icon()->SetImage(ui::ImageModel::FromVectorIcon(
+        vector_icons::kKeyboardIcon, cros_tokens::kCrosSysPrimary));
+    add_access_code_device_->text_label()->SetEnabledColorId(
+        cros_tokens::kCrosSysPrimary);
+  }
+}
+
+void CastDetailedView::AddReceiverActionButtons(
+    const CastSink& sink,
+    const CastRoute& route,
+    HoverHighlightView* receiver_view,
+    views::View* receiver_list_view) {
+  std::unique_ptr<PillButton> stop_button = CreateStopButton(route);
+
+  // In the case that we want to show a pause/resume button, then we must
+  // put both buttons on a row below the cast sink.
+  if (route.freeze_info.can_freeze) {
+    std::unique_ptr<PillButton> freeze_button = CreateFreezeButton(route);
+    std::unique_ptr<views::View> button_container = MakeButtonContainer();
+    std::vector<views::View*> extra_views;
+    extra_views.emplace_back(
+        button_container->AddChildView(std::move(freeze_button)));
+    extra_views.emplace_back(
+        button_container->AddChildView(std::move(stop_button)));
+    sink_extra_views_map_[sink.id] = extra_views;
+
+    // Add the button container directly as a new row in the list of cast
+    // devices. Since the associated device was just added, the buttons will
+    // show up correctly below their associated device.
+    receiver_list_view->AddChildView(std::move(button_container));
+  } else {
+    receiver_view->AddRightView(stop_button.release(),
+                                views::CreateEmptyBorder(gfx::Insets::TLBR(
+                                    0, 0, 0, kStopButtonExtraMargin)));
+  }
+}
+
+std::unique_ptr<PillButton> CastDetailedView::CreateStopButton(
+    const CastRoute& route) {
+  std::unique_ptr<PillButton> stop_button = std::make_unique<PillButton>(
+      base::BindRepeating(&CastDetailedView::StopCasting,
+                          base::Unretained(this), route.id),
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_STOP_CASTING),
+      PillButton::kDefaultWithIconLeading, &kQuickSettingsCircleStopIcon);
+  stop_button->SetBackgroundColorId(cros_tokens::kCrosSysErrorContainer);
+  stop_button->SetIconColorId(cros_tokens::kCrosSysError);
+  stop_button->SetButtonTextColorId(cros_tokens::kCrosSysError);
+  return stop_button;
+}
+
+std::unique_ptr<PillButton> CastDetailedView::CreateFreezeButton(
+    const CastRoute& route) {
+  std::unique_ptr<PillButton> freeze_button = std::make_unique<PillButton>(
+      base::BindRepeating(&CastDetailedView::FreezePressed,
+                          base::Unretained(this), route.id,
+                          route.freeze_info.is_frozen),
+      route.freeze_info.is_frozen
+          ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_RESUME_CASTING)
+          : l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_PAUSE_CASTING),
+      PillButton::kSecondaryWithIconLeading,
+      route.freeze_info.is_frozen ? &kQuickSettingsCirclePlayIcon
+                                  : &kQuickSettingsCirclePauseIcon);
+  return freeze_button;
+}
+
 BEGIN_METADATA(CastDetailedView, TrayDetailedView)
 END_METADATA
 
diff --git a/ash/system/cast/cast_detailed_view.h b/ash/system/cast/cast_detailed_view.h
index b8e21e1939ae4..4d1c7522f0d23 100644
--- a/ash/system/cast/cast_detailed_view.h
+++ b/ash/system/cast/cast_detailed_view.h
@@ -22,6 +22,7 @@ class View;
 namespace ash {
 
 class HoverHighlightView;
+class PillButton;
 
 // This view displays a list of cast receivers that can be clicked on and casted
 // to. It is activated by clicking on the chevron inside of
@@ -61,12 +62,38 @@ class ASH_EXPORT CastDetailedView : public TrayDetailedView,
   // Stops casting the route identified by `route_id`.
   void StopCasting(const std::string& route_id);
 
+  // Pauses or resumes the route given by `route_id`.
+  void FreezePressed(const std::string& route_id, bool is_frozen);
+
+  // Remove all child views from CastDetailedView.
+  void RemoveAllViews();
+
+  // Adds a button which allows for adding a device using an access code.
+  void AddAccessCodeCastButton(views::View* receiver_list_view);
+
+  // Adds buttons associated with a receiver so the user may perform route
+  // actions like stopping or pausing.
+  void AddReceiverActionButtons(const CastSink& sink,
+                                const CastRoute& route,
+                                HoverHighlightView* receiver_view,
+                                views::View* receiver_list_view);
+
+  // Creates a stop button which, when pressed, stops the associated `route`.
+  std::unique_ptr<PillButton> CreateStopButton(const CastRoute& route);
+
+  // Creates a freeze button which, when pressed, pauses / resumes the
+  // associated `route`.
+  std::unique_ptr<PillButton> CreateFreezeButton(const CastRoute& route);
+
   // A mapping from the sink id to the receiver/activity data.
   std::map<std::string, SinkAndRoute> sinks_and_routes_;
 
   // A mapping from the view pointer to the associated activity sink id.
   std::map<views::View*, std::string> view_to_sink_map_;
 
+  // A mapping of sink id to the associated extra views.
+  std::map<std::string, std::vector<views::View*>> sink_extra_views_map_;
+
   // Special list item that, if clicked, launches the access code casting dialog
   raw_ptr<HoverHighlightView, ExperimentalAsh> add_access_code_device_ =
       nullptr;
diff --git a/ash/system/cast/cast_detailed_view_unittest.cc b/ash/system/cast/cast_detailed_view_unittest.cc
index 88e821eb7d06f..fb30dc4780d91 100644
--- a/ash/system/cast/cast_detailed_view_unittest.cc
+++ b/ash/system/cast/cast_detailed_view_unittest.cc
@@ -57,6 +57,10 @@ class CastDetailedViewTest : public AshTestBase {
     return views;
   }
 
+  std::vector<views::View*> GetExtraViewsForSink(const std::string& sink_id) {
+    return detailed_view_->sink_extra_views_map_[sink_id];
+  }
+
   views::View* GetZeroStateView() { return detailed_view_->zero_state_view_; }
 
   // Adds two simulated cast devices.
@@ -226,4 +230,33 @@ TEST_F(CastDetailedViewTest, NoStopCastingButtonForNonLocalSource) {
   EXPECT_FALSE(row->right_view());
 }
 
+TEST_F(CastDetailedViewTest, FreezeButton) {
+  // Set up a fake sink and route, as if this Chromebook is casting to the
+  // device. And, the route may be frozen.
+  std::vector<SinkAndRoute> devices;
+  SinkAndRoute device;
+  device.sink.id = "fake_sink_id_1";
+  device.sink.name = "Sink Name 1";
+  device.sink.sink_icon_type = SinkIconType::kCast;
+  device.route.id = "fake_route_id_1";
+  device.route.title = "Title 1";
+  // Simulate a local source (this Chromebook).
+  device.route.is_local_source = true;
+  device.route.freeze_info.can_freeze = true;
+  devices.push_back(device);
+  OnDevicesUpdated(devices);
+
+  std::vector<views::View*> views = GetExtraViewsForSink("fake_sink_id_1");
+  ASSERT_EQ(views.size(), 2u);
+  auto* freeze_button = views[0];
+  EXPECT_TRUE(views::IsViewClass<PillButton>(freeze_button));
+  EXPECT_EQ(freeze_button->GetTooltipText(gfx::Point()), u"Pause casting");
+
+  // Clicking on the button pauses casting.
+  LeftClickOn(freeze_button);
+  EXPECT_EQ(cast_config_.freeze_route_count(), 1u);
+  EXPECT_EQ(cast_config_.freeze_route_route_id(), "fake_route_id_1");
+  EXPECT_EQ(delegate_->close_bubble_call_count(), 1u);
+}
+
 }  // namespace ash