chromium/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.cc

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

#include "chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.h"

#include "ash/shell.h"
#include "ash/system/cast/media_cast_audio_selector_view.h"
#include "ash/system/media/media_color_theme.h"
#include "ash/system/media/media_notification_provider.h"
#include "ash/system/media/media_notification_provider_observer.h"
#include "ash/system/media/media_tray.h"
#include "ash/system/status_area_widget.h"
#include "base/functional/callback_forward.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/media_ui_ash.h"
#include "chrome/browser/media/router/media_router_feature.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/global_media_controls/cast_media_notification_producer_keyed_service.h"
#include "chrome/browser/ui/ash/global_media_controls/cast_media_notification_producer_keyed_service_factory.h"
#include "chrome/browser/ui/global_media_controls/supplemental_device_picker_producer.h"
#include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h"
#include "chrome/browser/ui/views/global_media_controls/media_item_ui_helper.h"
#include "components/global_media_controls/public/media_item_manager.h"
#include "components/global_media_controls/public/media_session_item_producer.h"
#include "components/global_media_controls/public/mojom/device_service.mojom.h"
#include "components/global_media_controls/public/views/media_item_ui_detailed_view.h"
#include "components/global_media_controls/public/views/media_item_ui_list_view.h"
#include "components/global_media_controls/public/views/media_item_ui_view.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/media_session/public/cpp/media_session_service.h"
#include "ui/views/view.h"

namespace ash {

MediaNotificationProviderImpl::MediaNotificationProviderImpl(
    media_session::MediaSessionService* service)
    : item_manager_(global_media_controls::MediaItemManager::Create()) {
  CHECK_EQ(nullptr, MediaNotificationProvider::Get());
  MediaNotificationProvider::Set(this);

  item_manager_->AddObserver(this);

  if (!service) {
    return;
  }
  mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote;
  mojo::Remote<media_session::mojom::MediaControllerManager>
      controller_manager_remote;

  // Connect to receive audio focus events.
  service->BindAudioFocusManager(
      audio_focus_remote.BindNewPipeAndPassReceiver());

  // Connect to the controller manager so we can create media controllers for
  // media sessions.
  service->BindMediaControllerManager(
      controller_manager_remote.BindNewPipeAndPassReceiver());

  media_session_item_producer_ =
      std::make_unique<global_media_controls::MediaSessionItemProducer>(
          std::move(audio_focus_remote), std::move(controller_manager_remote),
          item_manager_.get(), /*source_id=*/std::nullopt);
  item_manager_->AddItemProducer(media_session_item_producer_.get());

  media_color_theme_ = GetCrosMediaColorTheme();
}

MediaNotificationProviderImpl::~MediaNotificationProviderImpl() {
  CHECK_EQ(this, MediaNotificationProvider::Get());
  MediaNotificationProvider::Set(nullptr);

  RemoveMediaItemManagerFromCastService(item_manager_.get());
  item_manager_->RemoveObserver(this);

  if (crosapi::CrosapiManager::IsInitialized()) {
    crosapi::CrosapiManager::Get()
        ->crosapi_ash()
        ->media_ui_ash()
        ->RemoveObserver(this);
  }
}

void MediaNotificationProviderImpl::AddObserver(
    ash::MediaNotificationProviderObserver* observer) {
  observers_.AddObserver(observer);
}

void MediaNotificationProviderImpl::RemoveObserver(
    ash::MediaNotificationProviderObserver* observer) {
  observers_.RemoveObserver(observer);
}

bool MediaNotificationProviderImpl::HasActiveNotifications() {
  if (!item_manager_) {
    return false;
  }
  return item_manager_->HasActiveItems();
}

bool MediaNotificationProviderImpl::HasFrozenNotifications() {
  if (!item_manager_) {
    return false;
  }
  return item_manager_->HasFrozenItems();
}

std::unique_ptr<views::View>
MediaNotificationProviderImpl::GetMediaNotificationListView(
    int separator_thickness,
    bool should_clip_height,
    global_media_controls::GlobalMediaControlsEntryPoint entry_point,
    const std::string& show_devices_for_item_id) {
  CHECK(item_manager_);
  CHECK(color_theme_);
  auto media_item_ui_list_view =
      std::make_unique<global_media_controls::MediaItemUIListView>(
          global_media_controls::MediaItemUIListView::SeparatorStyle(
              color_theme_->separator_color, separator_thickness),
          should_clip_height);
  media_item_ui_list_view_ = media_item_ui_list_view->GetWeakPtr();
  entry_point_ = entry_point;
  show_devices_for_item_id_ = show_devices_for_item_id;
  item_manager_->SetDialogDelegate(this);
  base::UmaHistogramEnumeration("Media.GlobalMediaControls.EntryPoint",
                                entry_point_);
  return media_item_ui_list_view;
}

void MediaNotificationProviderImpl::OnBubbleClosing() {
  item_manager_->SetDialogDelegate(nullptr);
}

void MediaNotificationProviderImpl::SetColorTheme(
    const media_message_center::NotificationTheme& color_theme) {
  color_theme_ = color_theme;
}

global_media_controls::MediaItemManager*
MediaNotificationProviderImpl::GetMediaItemManager() {
  return item_manager_.get();
}

void MediaNotificationProviderImpl::OnPrimaryUserSessionStarted() {
  // Since the user profile is now active, we can create a
  // CastMediaNotificationProducer for the MediaItemManager to access Cast media
  // items.
  cast_service_ =
      CastMediaNotificationProducerKeyedServiceFactory::GetForProfile(
          GetProfile());
  AddMediaItemManagerToCastService(item_manager_.get());

  if (!media_router::GlobalMediaControlsCastStartStopEnabled(GetProfile()) ||
      !crosapi::CrosapiManager::IsInitialized()) {
    return;
  }
  supplemental_device_picker_producer_ =
      std::make_unique<SupplementalDevicePickerProducer>(item_manager_.get());
  item_manager_->AddItemProducer(supplemental_device_picker_producer_.get());
  crosapi::MediaUIAsh* media_ui =
      crosapi::CrosapiManager::Get()->crosapi_ash()->media_ui_ash();
  media_ui->AddObserver(this);

  for (const auto& device_service : media_ui->device_services()) {
    device_service.second->SetDevicePickerProvider(
        supplemental_device_picker_producer_->PassRemote());
  }
}

void MediaNotificationProviderImpl::AddMediaItemManagerToCastService(
    global_media_controls::MediaItemManager* media_item_manager) {
  // Cast service will not be created in tests.
  if (cast_service_) {
    cast_service_->AddMediaItemManager(media_item_manager);
  }
}

void MediaNotificationProviderImpl::RemoveMediaItemManagerFromCastService(
    global_media_controls::MediaItemManager* media_item_manager) {
  if (cast_service_) {
    cast_service_->RemoveMediaItemManager(media_item_manager);
  }
}

std::unique_ptr<global_media_controls::MediaItemUIDeviceSelector>
MediaNotificationProviderImpl::BuildDeviceSelectorView(
    const std::string& id,
    base::WeakPtr<media_message_center::MediaNotificationItem> item,
    global_media_controls::GlobalMediaControlsEntryPoint entry_point,
    bool show_devices) {
  // Returns the Ash `MediaCastAudioSelectorView` if BackgroundListening feature
  // is enabled.
  if (base::FeatureList::IsEnabled(media::kBackgroundListening)) {
    auto* const profile = GetProfile();
    auto* const device_service = GetDeviceService(item);
    if (!ShouldShowDeviceSelectorView(profile, device_service, id, item,
                                      &device_selector_delegate_)) {
      return nullptr;
    }

    auto device_set = CreateHostAndClient(profile, id, item, device_service);

    return std::make_unique<MediaCastAudioSelectorView>(
        std::move(device_set.host), std::move(device_set.client),
        GetStopCastingCallback(profile, id, item), show_devices);
  }

  return BuildDeviceSelector(id, item, GetDeviceService(item),
                             &device_selector_delegate_, GetProfile(),
                             entry_point, show_devices, media_color_theme_);
}

std::unique_ptr<global_media_controls::MediaItemUIFooter>
MediaNotificationProviderImpl::BuildFooterView(
    const std::string& id,
    base::WeakPtr<media_message_center::MediaNotificationItem> item) {
  return BuildFooter(id, item, GetProfile(), media_color_theme_);
}

global_media_controls::MediaItemUI*
MediaNotificationProviderImpl::ShowMediaItem(
    const std::string& id,
    base::WeakPtr<media_message_center::MediaNotificationItem> item) {
  if (!media_item_ui_list_view_) {
    return nullptr;
  }

  bool show_devices =
      (!show_devices_for_item_id_.empty() && (id == show_devices_for_item_id_));
  auto media_display_page =
      (MediaTray::IsPinnedToShelf() ? global_media_controls::MediaDisplayPage::
                                          kSystemShelfMediaDetailedView
                                    : global_media_controls::MediaDisplayPage::
                                          kQuickSettingsMediaDetailedView);
  auto item_ui = std::make_unique<global_media_controls::MediaItemUIView>(
      id, item, BuildFooterView(id, item),
      BuildDeviceSelectorView(id, item, entry_point_, show_devices),
      color_theme_, media_color_theme_, media_display_page);
  auto* item_ui_ptr = item_ui.get();
  item_ui_observer_set_.Observe(id, item_ui_ptr);

  media_item_ui_list_view_->ShowItem(id, std::move(item_ui));
  for (auto& observer : observers_) {
    observer.OnNotificationListViewSizeChanged();
  }
  return item_ui_ptr;
}

void MediaNotificationProviderImpl::HideMediaItem(const std::string& id) {
  if (!media_item_ui_list_view_) {
    return;
  }
  media_item_ui_list_view_->HideItem(id);
  for (auto& observer : observers_) {
    observer.OnNotificationListViewSizeChanged();
  }
}

void MediaNotificationProviderImpl::RefreshMediaItem(
    const std::string& id,
    base::WeakPtr<media_message_center::MediaNotificationItem> item) {
  if (!media_item_ui_list_view_) {
    return;
  }

  bool show_devices =
      (!show_devices_for_item_id_.empty() && (id == show_devices_for_item_id_));
  auto* media_item_ui = media_item_ui_list_view_->GetItem(id);
  media_item_ui->UpdateFooterView(BuildFooterView(id, item));
  media_item_ui->UpdateDeviceSelector(
      BuildDeviceSelectorView(id, item, entry_point_, show_devices));

  for (auto& observer : observers_) {
    observer.OnNotificationListViewSizeChanged();
  }
}

void MediaNotificationProviderImpl::HideMediaDialog() {
  ash::StatusAreaWidget::ForWindow(ash::Shell::Get()->GetPrimaryRootWindow())
      ->media_tray()
      ->CloseBubble();
}

void MediaNotificationProviderImpl::OnItemListChanged() {
  for (auto& observer : observers_) {
    observer.OnNotificationListChanged();
  }
}

void MediaNotificationProviderImpl::OnMediaItemUISizeChanged() {
  for (auto& observer : observers_) {
    observer.OnNotificationListViewSizeChanged();
  }
}

void MediaNotificationProviderImpl::OnDeviceServiceRegistered(
    global_media_controls::mojom::DeviceService* device_service) {
  device_service->SetDevicePickerProvider(
      supplemental_device_picker_producer_->PassRemote());
}

global_media_controls::mojom::DeviceService*
MediaNotificationProviderImpl::GetDeviceService(
    base::WeakPtr<media_message_center::MediaNotificationItem> item) const {
  if (!item || !item->GetSourceId()) {
    return nullptr;
  }
  if (device_service_for_testing_) {
    return device_service_for_testing_;
  }
  return crosapi::CrosapiManager::Get()
      ->crosapi_ash()
      ->media_ui_ash()
      ->GetDeviceService(*item->GetSourceId());
}

Profile* MediaNotificationProviderImpl::GetProfile() {
  return profile_for_testing_ ? profile_for_testing_.get()
                              : ProfileManager::GetActiveUserProfile();
}

}  // namespace ash