chromium/ash/system/cast/media_cast_audio_selector_view.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/cast/media_cast_audio_selector_view.h"

#include <utility>

#include "ash/system/audio/output_audio_sliders_view.h"
#include "ash/system/cast/media_cast_list_view.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/observer_list.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "components/global_media_controls/public/views/media_item_ui_view.h"
#include "components/vector_icons/vector_icons.h"
#include "media/audio/audio_device_description.h"
#include "media/base/media_switches.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/views/background.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"

namespace ash {

namespace {
// Constants for the `MediaCastAudioSelectorView`

// Using a fixed width for this view which will be CHECKed on the global media
// side (`media_item_ui_view`).
constexpr int kSelectorViewWidth = 400;

}  // namespace

MediaCastAudioSelectorView::MediaCastAudioSelectorView(
    mojo::PendingRemote<global_media_controls::mojom::DeviceListHost>
        device_list_host,
    mojo::PendingReceiver<global_media_controls::mojom::DeviceListClient>
        receiver,
    base::RepeatingClosure stop_casting_callback,
    bool show_devices)
    : device_list_host_(std::move(device_list_host)) {
  views::Builder<views::View>(this)
      .SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT))
      .SetLayoutManager(std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kVertical))
      .SetPreferredSize(
          gfx::Size(kSelectorViewWidth, GetHeightForWidth(kSelectorViewWidth)))
      .AddChildren(
          views::Builder<views::BoxLayoutView>()
              .CopyAddressTo(&list_view_container_)
              .SetID(kListViewContainerId)
              .SetOrientation(views::BoxLayout::Orientation::kVertical)
              .SetVisible(false)
              .AddChildren(
                  views::Builder<views::View>(
                      std::make_unique<OutputAudioSlidersView>(
                          base::BindRepeating(
                              &MediaCastAudioSelectorView::OnDevicesUpdated,
                              base::Unretained(this),
                              DeviceType::kAudioDevice)))
                      .SetID(kMediaAudioListViewId))
              .AddChildren(
                  views::Builder<views::View>(
                      std::make_unique<MediaCastListView>(
                          std::move(stop_casting_callback),
                          base::BindRepeating(
                              &MediaCastAudioSelectorView::OnCastDeviceSelected,
                              base::Unretained(this)),
                          base::BindRepeating(
                              &MediaCastAudioSelectorView::OnDevicesUpdated,
                              base::Unretained(this), DeviceType::kCastDevice),
                          std::move(receiver)))
                      .SetID(kMediaCastListViewId)))
      .BuildChildren();

  if (show_devices) {
    ShowDevices();
  }

  DeprecatedLayoutImmediately();
}

MediaCastAudioSelectorView::~MediaCastAudioSelectorView() = default;

void MediaCastAudioSelectorView::SetMediaItemUIView(
    global_media_controls::MediaItemUIView* view) {
  media_item_ui_ = view;
}

// The colors are set with ash color ids, so we don't need to override this
// method.
void MediaCastAudioSelectorView::OnColorsChanged(SkColor foreground_color,
                                                 SkColor background_color) {}

// This audio device related feature (under the flag
// `media::kGlobalMediaControlsSeamlessTransfer`) were never launched, so no
// longer maintained. On ash side here we override it with an empty
// implementation, and we add our own output audio list to this view.
void MediaCastAudioSelectorView::UpdateCurrentAudioDevice(
    const std::string& current_device_id) {}

void MediaCastAudioSelectorView::MediaCastAudioSelectorView::ShowDevices() {
  DCHECK(!is_expanded_);
  is_expanded_ = true;
  NotifyAccessibilityEvent(ax::mojom::Event::kExpandedChanged, true);

  list_view_container_->SetVisible(true);

  // Focus the first available device when the device list is shown for
  // accessibility.
  // TODO(b/327507429): Revisit the logic here when the audio list view is
  // added to see which view should be focused on.
  if (const auto& children = list_view_container_->children();
      !children.empty()) {
    children[0]->RequestFocus();
  }
}

void MediaCastAudioSelectorView::HideDevices() {
  DCHECK(is_expanded_);
  is_expanded_ = false;
  NotifyAccessibilityEvent(ax::mojom::Event::kExpandedChanged, true);

  list_view_container_->SetVisible(false);
  PreferredSizeChanged();
}

bool MediaCastAudioSelectorView::IsDeviceSelectorExpanded() {
  return is_expanded_;
}

void MediaCastAudioSelectorView::OnDevicesUpdated(DeviceType device_type,
                                                  bool has_devices) {
  device_type_bits_[static_cast<int>(device_type)] = has_devices;

  if (media_item_ui_) {
    media_item_ui_->OnDeviceSelectorViewDevicesChanged(device_type_bits_.any());
  }
}

void MediaCastAudioSelectorView::OnCastDeviceSelected(
    const std::string& device_id) {
  if (device_list_host_) {
    device_list_host_->SelectDevice(device_id);
  }
}

BEGIN_METADATA(MediaCastAudioSelectorView)
END_METADATA

}  // namespace ash