| // Copyright 2019 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/global_media_controls/media_notification_service.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/callback_list.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/unguessable_token.h" |
| #include "chrome/browser/feature_engagement/tracker_factory.h" |
| #include "chrome/browser/media/router/media_router_feature.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" |
| #include "chrome/browser/ui/global_media_controls/cast_device_list_host.h" |
| #include "chrome/browser/ui/global_media_controls/media_notification_device_provider_impl.h" |
| #include "chrome/browser/ui/global_media_controls/presentation_request_notification_producer.h" |
| #include "chrome/browser/ui/media_router/cast_dialog_controller.h" |
| #include "chrome/browser/ui/media_router/media_router_ui.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/buildflags.h" |
| #include "components/feature_engagement/public/tracker.h" |
| #include "components/global_media_controls/public/media_dialog_delegate.h" |
| #include "components/global_media_controls/public/media_item_manager.h" |
| #include "components/global_media_controls/public/media_item_producer.h" |
| #include "components/media_message_center/media_notification_item.h" |
| #include "components/media_router/browser/media_router_factory.h" |
| #include "components/media_router/browser/presentation/start_presentation_context.h" |
| #include "components/media_router/browser/presentation/web_contents_presentation_manager.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "content/public/browser/audio_service.h" |
| #include "content/public/browser/media_session.h" |
| #include "content/public/browser/media_session_service.h" |
| #include "media/base/media_switches.h" |
| #include "media/remoting/device_capability_checker.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "services/media_session/public/mojom/media_session.mojom.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/system/media/media_notification_provider.h" |
| #include "ash/system/media/media_tray.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/system/unified/unified_system_tray.h" |
| #include "ash/system/unified/unified_system_tray_bubble.h" |
| #include "ash/system/unified/unified_system_tray_controller.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| #include "chrome/browser/glic/host/host.h" |
| #include "chrome/browser/glic/public/glic_keyed_service.h" |
| #include "chrome/browser/glic/widget/glic_window_controller.h" |
| #endif |
| |
| namespace mojom { |
| using global_media_controls::mojom::DeviceListClient; |
| using global_media_controls::mojom::DeviceListHost; |
| } // namespace mojom |
| |
| namespace { |
| |
| // The maximum number of actions we will record to UKM for a specific source. |
| constexpr int kMaxActionsRecordedToUKM = 100; |
| |
| void CancelRequest( |
| std::unique_ptr<media_router::StartPresentationContext> context, |
| const std::string& message) { |
| context->InvokeErrorCallback(blink::mojom::PresentationError( |
| blink::mojom::PresentationErrorType::PRESENTATION_REQUEST_CANCELLED, |
| message)); |
| } |
| |
| // Here we check to see if the WebContents is focused. Note that we can't just |
| // use |WebContentsObserver::OnWebContentsFocused()| and |
| // |WebContentsObserver::OnWebContentsLostFocus()| because focusing the |
| // MediaDialogView causes the WebContents to "lose focus", so we'd never be |
| // focused. |
| bool IsWebContentsFocused(content::WebContents* web_contents) { |
| DCHECK(web_contents); |
| Browser* browser = chrome::FindBrowserWithTab(web_contents); |
| if (!browser) { |
| return false; |
| } |
| // If the given WebContents is not in the focused window, then it's not |
| // focused. Note that we know a Browser is focused because otherwise the user |
| // could not interact with the MediaDialogView. |
| if (GetLastActiveBrowserWindowInterfaceWithAnyProfile() != browser) { |
| return false; |
| } |
| return browser->tab_strip_model()->GetActiveWebContents() == web_contents; |
| } |
| |
| bool ShouldInitializeWithRemotePlaybackSource( |
| content::WebContents* web_contents, |
| media_session::mojom::RemotePlaybackMetadataPtr remote_playback_metadata) { |
| if (!base::FeatureList::IsEnabled(media::kMediaRemotingWithoutFullscreen)) { |
| return false; |
| } |
| |
| // Do not initialize MediaRouterUI with RemotePlayback media source when there |
| // exists default presentation request. |
| base::WeakPtr<media_router::WebContentsPresentationManager> |
| presentation_manager = |
| media_router::WebContentsPresentationManager::Get(web_contents); |
| if (presentation_manager && |
| presentation_manager->HasDefaultPresentationRequest()) { |
| return false; |
| } |
| |
| if (!remote_playback_metadata) { |
| return false; |
| } |
| |
| if (media::remoting::ParseVideoCodec(remote_playback_metadata->video_codec) == |
| media::VideoCodec::kUnknown || |
| media::remoting::ParseAudioCodec(remote_playback_metadata->audio_codec) == |
| media::AudioCodec::kUnknown) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| MediaNotificationService::MediaNotificationService(Profile* profile, |
| bool show_from_all_profiles) |
| : profile_(profile), receiver_(this) { |
| item_manager_ = global_media_controls::MediaItemManager::Create(); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (auto* provider = ash::MediaNotificationProvider::Get(); provider) { |
| provider_observation_.Observe(provider); |
| } |
| #endif |
| |
| std::optional<base::UnguessableToken> source_id; |
| if (!show_from_all_profiles) { |
| source_id = content::MediaSession::GetSourceId(profile); |
| } |
| |
| mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote; |
| mojo::Remote<media_session::mojom::MediaControllerManager> |
| controller_manager_remote; |
| |
| // Connect to receive audio focus events. |
| content::GetMediaSessionService().BindAudioFocusManager( |
| audio_focus_remote.BindNewPipeAndPassReceiver()); |
| |
| // Connect to the controller manager so we can create media controllers for |
| // media sessions. |
| content::GetMediaSessionService().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); |
| |
| // It is safe to use `base::Unretained` here because |
| // `media_session_item_producer_` is owned by `this`. |
| media_session_item_producer_->SetIsIdBlockedCallback(base::BindRepeating( |
| &MediaNotificationService::IsIdBlocked, base::Unretained(this))); |
| |
| media_session_item_producer_->AddObserver(this); |
| item_manager_->AddItemProducer(media_session_item_producer_.get()); |
| |
| if (!media_router::MediaRouterEnabled(profile)) { |
| return; |
| } |
| // CastMediaNotificationProducer is owned by |
| // CastMediaNotificationProducerKeyedService in Ash. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| // base::Unretained() is safe here because `cast_notification_producer_` is |
| // deleted before `item_manager_`. |
| cast_notification_producer_ = std::make_unique<CastMediaNotificationProducer>( |
| profile, item_manager_.get()); |
| item_manager_->AddItemProducer(cast_notification_producer_.get()); |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| presentation_request_notification_producer_ = |
| std::make_unique<PresentationRequestNotificationProducer>( |
| base::BindRepeating( |
| &MediaNotificationService::HasActiveNotificationsForWebContents, |
| base::Unretained(this)), |
| content::MediaSession::GetSourceId(profile)); |
| |
| auto* item_manager = GetMediaItemManagerForSupplementalDevicePickerProducer(); |
| if (item_manager) { |
| supplemental_device_picker_producer_ = std::make_unique< |
| global_media_controls::SupplementalDevicePickerProducer>(item_manager); |
| item_manager->AddItemProducer(supplemental_device_picker_producer_.get()); |
| SetDevicePickerProvider(supplemental_device_picker_producer_->PassRemote()); |
| } |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void MediaNotificationService::ShowDialogAsh( |
| std::unique_ptr<media_router::StartPresentationContext> context) { |
| auto* web_contents = content::WebContents::FromRenderFrameHost( |
| content::RenderFrameHost::FromID( |
| context->presentation_request().render_frame_host_id)); |
| OnStartPresentationContextCreated(std::move(context)); |
| auto routes = media_router::WebContentsPresentationManager::Get(web_contents) |
| ->GetMediaRoutes(); |
| std::string item_id; |
| if (!routes.empty()) { |
| // When `routes` is not empty, we'd ideally set `item_id` to be the ID of a |
| // MediaRoute so that we'd only show the corresponding notification item. |
| item_id = routes.begin()->media_route_id(); |
| } else { |
| item_id = content::MediaSession::GetRequestIdFromWebContents(web_contents) |
| .ToString(); |
| } |
| |
| // Keep Media Tray pinned to use a separate widget in kiosk sessions because |
| // the Unified System Tray bubble is not available. |
| if (ash::Shell::Get()->session_controller()->IsRunningInAppMode()) { |
| ash::MediaTray::SetPinnedToShelf(true); |
| } |
| |
| if (ash::MediaTray::IsPinnedToShelf()) { |
| ash::StatusAreaWidget::ForWindow(ash::Shell::Get()->GetPrimaryRootWindow()) |
| ->media_tray() |
| ->ShowBubbleWithItem(item_id); |
| } else { |
| ash::UnifiedSystemTray* tray = |
| ash::StatusAreaWidget::ForWindow( |
| ash::Shell::Get()->GetPrimaryRootWindow()) |
| ->unified_system_tray(); |
| tray->ShowBubble(); |
| tray->bubble() |
| ->unified_system_tray_controller() |
| ->ShowMediaControlsDetailedView( |
| global_media_controls::GlobalMediaControlsEntryPoint::kPresentation, |
| item_id); |
| } |
| } |
| |
| void MediaNotificationService::OnMediaNotificationProviderWillBeDestroyed() { |
| if (supplemental_device_picker_producer_) { |
| if (auto* item_manager = |
| GetMediaItemManagerForSupplementalDevicePickerProducer()) { |
| item_manager->RemoveItemProducer( |
| supplemental_device_picker_producer_.get()); |
| } |
| supplemental_device_picker_producer_.reset(); |
| } |
| provider_observation_.Reset(); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| MediaNotificationService::~MediaNotificationService() { |
| media_session_item_producer_->RemoveObserver(this); |
| item_manager_->RemoveItemProducer(media_session_item_producer_.get()); |
| |
| if (supplemental_device_picker_producer_) { |
| if (auto* item_manager = |
| GetMediaItemManagerForSupplementalDevicePickerProducer()) { |
| item_manager->RemoveItemProducer( |
| supplemental_device_picker_producer_.get()); |
| } |
| } |
| } |
| |
| void MediaNotificationService::Shutdown() { |
| shutdown_has_started_ = true; |
| // `cast_notification_producer_`, |
| // `presentation_request_notification_producer_` and `host_receivers_` |
| // depend on MediaRouter, which is another keyed service. So they must be |
| // destroyed here. |
| if (cast_notification_producer_) { |
| item_manager_->RemoveItemProducer(cast_notification_producer_.get()); |
| } |
| cast_notification_producer_.reset(); |
| presentation_request_notification_producer_.reset(); |
| for (const auto& host : host_receivers_) { |
| if (host.second) { |
| host.second->Close(); |
| } |
| } |
| } |
| |
| void MediaNotificationService::OnAudioSinkChosen(const std::string& item_id, |
| const std::string& sink_id) { |
| media_session_item_producer_->SetAudioSinkId(item_id, sink_id); |
| } |
| |
| base::CallbackListSubscription |
| MediaNotificationService::RegisterAudioOutputDeviceDescriptionsCallback( |
| MediaNotificationDeviceProvider::GetOutputDevicesCallback callback) { |
| if (!device_provider_) { |
| device_provider_ = std::make_unique<MediaNotificationDeviceProviderImpl>( |
| content::CreateAudioSystemForAudioService()); |
| } |
| return device_provider_->RegisterOutputDeviceDescriptionsCallback( |
| std::move(callback)); |
| } |
| |
| base::CallbackListSubscription |
| MediaNotificationService::RegisterIsAudioOutputDeviceSwitchingSupportedCallback( |
| const std::string& id, |
| base::RepeatingCallback<void(bool)> callback) { |
| return media_session_item_producer_ |
| ->RegisterIsAudioOutputDeviceSwitchingSupportedCallback( |
| id, std::move(callback)); |
| } |
| |
| void MediaNotificationService::OnMediaRemotingRequested( |
| const std::string& item_id) { |
| auto item = media_session_item_producer_->GetMediaItem(item_id); |
| if (!item) { |
| return; |
| } |
| |
| item->RequestMediaRemoting(); |
| auto* web_contents = |
| content::MediaSession::GetWebContentsFromRequestId(item_id); |
| if (web_contents && web_contents->GetLastCommittedURL().SchemeIsFile()) { |
| feature_engagement::TrackerFactory::GetForBrowserContext(profile_) |
| ->NotifyEvent("media_route_started_from_gmc"); |
| } |
| } |
| |
| void MediaNotificationService::OnSinksDiscovered(const std::string& item_id) { |
| auto item = media_session_item_producer_->GetMediaItem(item_id); |
| auto* web_contents = |
| content::MediaSession::GetWebContentsFromRequestId(item_id); |
| |
| if (web_contents) { |
| should_show_cast_local_media_iph_ = |
| web_contents->GetLastCommittedURL().SchemeIsFile(); |
| } |
| } |
| |
| void MediaNotificationService::OnMediaSessionActionButtonPressed( |
| const std::string& id, |
| media_session::mojom::MediaSessionAction action) { |
| auto* web_contents = content::MediaSession::GetWebContentsFromRequestId(id); |
| if (!web_contents) { |
| return; |
| } |
| base::UmaHistogramBoolean("Media.GlobalMediaControls.UserActionFocus", |
| IsWebContentsFocused(web_contents)); |
| |
| ukm::UkmRecorder* recorder = ukm::UkmRecorder::Get(); |
| ukm::SourceId source_id = |
| web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId(); |
| |
| if (++actions_recorded_to_ukm_[source_id] > kMaxActionsRecordedToUKM) { |
| return; |
| } |
| ukm::builders::Media_GlobalMediaControls_ActionButtonPressed(source_id) |
| .SetMediaSessionAction(static_cast<int64_t>(action)) |
| .Record(recorder); |
| } |
| |
| void MediaNotificationService::SetDialogDelegateForWebContents( |
| global_media_controls::MediaDialogDelegate* delegate, |
| content::WebContents* contents) { |
| DCHECK(delegate); |
| DCHECK(contents); |
| |
| // When the dialog is opened by a PresentationRequest, there should be only |
| // one notification, in the following priority order: |
| // 1. A cast presentation session associated with `contents`. |
| // 2. A local media session associated with `contents`. This media session |
| // might potentially be associated with a Remote Playback route. |
| // 3. A supplemental notification populated using the PresentationRequest. |
| std::string item_id; |
| |
| // Find the cast presentation route associated with `contents`. |
| // WebContentsPresentationManager manages all presentation routes including |
| // Cast and Remote Playback presentations. For the sake of displaying media |
| // routes in the GMC dialog, Cast presentation routes should be shown as Cast |
| // notification items and Remote Playback presentation routes should be shown |
| // as media session notification items. |
| std::optional<std::string> cast_presentation_route_id; |
| for (const auto& route : |
| media_router::WebContentsPresentationManager::Get(contents) |
| ->GetMediaRoutes()) { |
| if (route.media_source().IsCastPresentationUrl()) { |
| cast_presentation_route_id = route.media_route_id(); |
| break; |
| } |
| } |
| |
| if (cast_presentation_route_id.has_value()) { |
| // It is possible for a sender page to connect to two routes. For the |
| // sake of the Zenith dialog, only one notification is needed. |
| item_id = cast_presentation_route_id.value(); |
| } else if (HasActiveControllableSessionForWebContents(contents)) { |
| item_id = GetActiveControllableSessionForWebContents(contents); |
| } else { |
| const global_media_controls::SupplementalDevicePickerItem& |
| supplemental_item = |
| supplemental_device_picker_producer_->GetOrCreateNotificationItem( |
| content::MediaSession::GetSourceId(profile_)); |
| item_id = supplemental_item.id(); |
| DCHECK(presentation_request_notification_producer_->GetWebContents() == |
| contents); |
| } |
| |
| item_manager_->SetDialogDelegateForId(delegate, item_id); |
| } |
| |
| bool MediaNotificationService::HasActiveNotificationsForWebContents( |
| content::WebContents* web_contents) const { |
| bool has_media_session = |
| HasActiveControllableSessionForWebContents(web_contents); |
| return HasCastNotificationsForWebContents(web_contents) || has_media_session; |
| } |
| |
| bool MediaNotificationService::HasLocalCastNotifications() const { |
| return cast_notification_producer_ |
| ? cast_notification_producer_->HasLocalMediaRoute() |
| : false; |
| } |
| |
| void MediaNotificationService::OnStartPresentationContextCreated( |
| std::unique_ptr<media_router::StartPresentationContext> context) { |
| auto* web_contents = content::WebContents::FromRenderFrameHost( |
| content::RenderFrameHost::FromID( |
| context->presentation_request().render_frame_host_id)); |
| if (!web_contents) { |
| CancelRequest(std::move(context), "The web page is closed."); |
| return; |
| } |
| |
| // If there exists a cast notification associated with `web_contents`, delete |
| // `context` because users should not start a new presentation at this time. |
| if (HasCastNotificationsForWebContents(web_contents)) { |
| CancelRequest(std::move(context), "A presentation has already started."); |
| } else if (HasActiveControllableSessionForWebContents(web_contents)) { |
| // If there exists a media session notification and a tab mirroring session, |
| // both, associated with `web_contents`, delete `context` because users |
| // should not start a new presentation at this time. |
| if (HasTabMirroringSessionForWebContents(web_contents)) { |
| CancelRequest(std::move(context), |
| "A tab mirroring session has already started."); |
| return; |
| } |
| |
| // If there exists a media session notification associated with |
| // |web_contents|, hold onto the context for later use. |
| context_ = std::move(context); |
| |
| // When a media session item is associated with PresentationRequest, we |
| // must show the origin associated with the request rather than that for |
| // the top frame. |
| std::string item_id = |
| GetActiveControllableSessionForWebContents(web_contents); |
| media_session_item_producer_->UpdateMediaItemSourceOrigin( |
| item_id, context_->presentation_request().frame_origin); |
| } else if (presentation_request_notification_producer_) { |
| // If there do not exist active notifications, pass |context| to |
| // |presentation_request_notification_producer_| to create a dummy |
| // notification. |
| presentation_request_notification_producer_ |
| ->OnStartPresentationContextCreated(std::move(context)); |
| } else { |
| CancelRequest(std::move(context), "Unable to start presentation."); |
| } |
| } |
| |
| void MediaNotificationService::GetDeviceListHostForSession( |
| const std::string& session_id, |
| mojo::PendingReceiver<mojom::DeviceListHost> host_receiver, |
| mojo::PendingRemote<mojom::DeviceListClient> client_remote) { |
| std::optional<std::string> remoting_session_id; |
| // `remoting_session_id` is used to construct the MediaRemotingCallback for |
| // CastDeviceListHost to request Media Remoting for a MediaSession. This is |
| // used for Media Remoting sessions started from the GMC dialog. However, when |
| // the dialog is opened for RemotePlayback#prompt() (when `context_` is not |
| // nullptr), the Remote Playback API on the blink side handles sending Media |
| // Remoting request and there's no need for requesting Media Remoting from |
| // MNS. |
| if (context_ == nullptr) { |
| remoting_session_id = session_id; |
| } |
| CreateCastDeviceListHost(CreateCastDialogControllerForSession(session_id), |
| std::move(host_receiver), std::move(client_remote), |
| remoting_session_id); |
| } |
| |
| void MediaNotificationService::GetDeviceListHostForPresentation( |
| mojo::PendingReceiver<mojom::DeviceListHost> host_receiver, |
| mojo::PendingRemote<mojom::DeviceListClient> client_remote) { |
| CreateCastDeviceListHost(CreateCastDialogControllerForPresentationRequest(), |
| std::move(host_receiver), std::move(client_remote), |
| std::nullopt); |
| } |
| |
| void MediaNotificationService::SetDevicePickerProvider( |
| mojo::PendingRemote<global_media_controls::mojom::DevicePickerProvider> |
| provider_remote) { |
| presentation_request_notification_producer_->BindProvider( |
| std::move(provider_remote)); |
| } |
| |
| std::unique_ptr<media_router::CastDialogController> |
| MediaNotificationService::CreateCastDialogControllerForSession( |
| const std::string& id) { |
| auto* web_contents = content::MediaSession::GetWebContentsFromRequestId(id); |
| if (!web_contents) { |
| return nullptr; |
| } |
| |
| if (context_) { |
| return media_router::MediaRouterUI::CreateWithStartPresentationContext( |
| web_contents, std::move(context_)); |
| } |
| |
| auto remote_playback_metadata = |
| media_session_item_producer_->GetRemotePlaybackMetadataFromItem(id); |
| if (ShouldInitializeWithRemotePlaybackSource( |
| web_contents, remote_playback_metadata.Clone())) { |
| return media_router::MediaRouterUI::CreateWithMediaSessionRemotePlayback( |
| web_contents, |
| media::remoting::ParseVideoCodec(remote_playback_metadata->video_codec), |
| media::remoting::ParseAudioCodec( |
| remote_playback_metadata->audio_codec)); |
| } |
| |
| return media_router::MediaRouterUI::CreateWithDefaultMediaSource( |
| web_contents); |
| } |
| |
| std::unique_ptr<media_router::CastDialogController> |
| MediaNotificationService::CreateCastDialogControllerForPresentationRequest() { |
| auto* web_contents = |
| presentation_request_notification_producer_->GetWebContents(); |
| if (!web_contents) { |
| return nullptr; |
| } |
| if (!presentation_request_notification_producer_->GetNotificationItem() |
| ->is_default_presentation_request()) { |
| return media_router::MediaRouterUI::CreateWithStartPresentationContext( |
| web_contents, |
| presentation_request_notification_producer_->GetNotificationItem() |
| ->PassContext()); |
| } |
| return media_router::MediaRouterUI::CreateWithDefaultMediaSource( |
| web_contents); |
| } |
| |
| void MediaNotificationService::CreateCastDeviceListHost( |
| std::unique_ptr<media_router::CastDialogController> dialog_controller, |
| mojo::PendingReceiver<mojom::DeviceListHost> host_pending_receiver, |
| mojo::PendingRemote<mojom::DeviceListClient> client_remote, |
| std::optional<std::string> remoting_session_id) { |
| if (!dialog_controller) { |
| // We discard the PendingReceiver/Remote here, and if they have disconnect |
| // handlers set, those get called. |
| return; |
| } |
| auto media_remoting_callback_ = |
| remoting_session_id.has_value() |
| ? base::BindRepeating( |
| &MediaNotificationService::OnMediaRemotingRequested, |
| weak_ptr_factory_.GetWeakPtr(), remoting_session_id.value()) |
| : base::DoNothing(); |
| auto on_sinks_discovered_callback = |
| remoting_session_id.has_value() |
| ? base::BindRepeating(&MediaNotificationService::OnSinksDiscovered, |
| weak_ptr_factory_.GetWeakPtr(), |
| remoting_session_id.value()) |
| : base::DoNothing(); |
| auto host = std::make_unique<CastDeviceListHost>( |
| std::move(dialog_controller), std::move(client_remote), |
| std::move(media_remoting_callback_), |
| base::BindRepeating(&global_media_controls::MediaItemManager::HideDialog, |
| item_manager_->GetWeakPtr()), |
| std::move(on_sinks_discovered_callback)); |
| int host_id = host->id(); |
| mojo::SelfOwnedReceiverRef<global_media_controls::mojom::DeviceListHost> |
| host_receiver = mojo::MakeSelfOwnedReceiver( |
| std::move(host), std::move(host_pending_receiver)); |
| host_receiver->set_connection_error_handler( |
| base::BindOnce(&MediaNotificationService::RemoveDeviceListHost, |
| weak_ptr_factory_.GetWeakPtr(), host_id)); |
| host_receivers_.emplace(host_id, std::move(host_receiver)); |
| } |
| |
| void MediaNotificationService::set_device_provider_for_testing( |
| std::unique_ptr<MediaNotificationDeviceProvider> device_provider) { |
| device_provider_ = std::move(device_provider); |
| } |
| |
| bool MediaNotificationService::HasCastNotificationsForWebContents( |
| content::WebContents* web_contents) const { |
| return !media_router::WebContentsPresentationManager::Get(web_contents) |
| ->GetMediaRoutes() |
| .empty(); |
| } |
| |
| bool MediaNotificationService::HasTabMirroringSessionForWebContents( |
| content::WebContents* web_contents) const { |
| if (!base::FeatureList::IsEnabled( |
| media_router::kFallbackToAudioTabMirroring)) { |
| return false; |
| } |
| |
| // Return true if there exists a tab mirroring session associated with |
| // `web_contents`. |
| const int item_tab_id = |
| sessions::SessionTabHelper::IdForTab(web_contents).id(); |
| for (const auto& route : |
| media_router::MediaRouterFactory::GetApiForBrowserContext( |
| web_contents->GetBrowserContext()) |
| ->GetCurrentRoutes()) { |
| media_router::MediaSource media_source = route.media_source(); |
| if (media_source.IsTabMirroringSource() && |
| media_source.TabId().has_value() && |
| media_source.TabId().value() == item_tab_id) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool MediaNotificationService::HasActiveControllableSessionForWebContents( |
| content::WebContents* web_contents) const { |
| DCHECK(web_contents); |
| auto item_ids = media_session_item_producer_->GetActiveControllableItemIds(); |
| return std::ranges::any_of(item_ids, [web_contents](const auto& item_id) { |
| return web_contents == |
| content::MediaSession::GetWebContentsFromRequestId(item_id); |
| }); |
| } |
| |
| std::string |
| MediaNotificationService::GetActiveControllableSessionForWebContents( |
| content::WebContents* web_contents) const { |
| DCHECK(web_contents); |
| for (const auto& item_id : |
| media_session_item_producer_->GetActiveControllableItemIds()) { |
| if (web_contents == |
| content::MediaSession::GetWebContentsFromRequestId(item_id)) { |
| return item_id; |
| } |
| } |
| return ""; |
| } |
| |
| void MediaNotificationService::RemoveDeviceListHost(int host_id) { |
| // If shutdown has started, then we may currently be iterating through |
| // `host_receivers_` so we should not erase from it. `host_receivers_` will |
| // get destroyed soon anyways. |
| if (!shutdown_has_started_) { |
| host_receivers_.erase(host_id); |
| } |
| } |
| |
| bool MediaNotificationService::IsIdBlocked( |
| const std::string& request_id) const { |
| #if BUILDFLAG(ENABLE_GLIC) |
| auto* glic_keyed_service = glic::GlicKeyedService::Get(profile_); |
| if (!glic_keyed_service) { |
| return false; |
| } |
| |
| // Block if the request came from any glic instance. |
| for (glic::GlicInstance* instance : |
| glic_keyed_service->window_controller().GetInstances()) { |
| if (!instance->host().webui_contents()) { |
| continue; |
| } |
| |
| std::vector<content::WebContents*> inner_contents = |
| instance->host().webui_contents()->GetInnerWebContents(); |
| if (inner_contents.size() == 1ul && |
| content::MediaSession::GetRequestIdFromWebContents(inner_contents[0]) |
| .ToString() == request_id) { |
| return true; |
| } |
| } |
| #endif |
| return false; |
| } |
| |
| global_media_controls::MediaItemManager* MediaNotificationService:: |
| GetMediaItemManagerForSupplementalDevicePickerProducer() { |
| #if BUILDFLAG(IS_CHROMEOS) |
| auto* media_notification_provider = ash::MediaNotificationProvider::Get(); |
| return media_notification_provider |
| ? media_notification_provider->GetMediaItemManager() |
| : nullptr; |
| #else |
| return item_manager_.get(); |
| #endif |
| } |