| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/media/router/mojo/media_router_desktop.h" |
| |
| #include "base/bind_helpers.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/media/router/media_router_factory.h" |
| #include "chrome/browser/media/router/media_router_feature.h" |
| #include "chrome/browser/media/router/mojo/media_route_controller.h" |
| #include "chrome/browser/media/router/mojo/media_router_mojo_metrics.h" |
| #include "chrome/browser/media/router/providers/cast/cast_media_route_provider.h" |
| #include "chrome/browser/media/router/providers/cast/chrome_cast_message_handler.h" |
| #include "chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/media_router/media_source_helper.h" |
| #include "components/cast_channel/cast_socket_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "extensions/common/extension.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #if defined(OS_WIN) |
| #include "chrome/browser/media/router/mojo/media_route_provider_util_win.h" |
| #endif |
| |
| namespace media_router { |
| |
| namespace { |
| |
| // Returns the Connector object for the current process. It is the caller's |
| // responsibility to clone the returned object to be used in another thread. |
| service_manager::Connector* GetConnector() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return content::ServiceManagerConnection::GetForProcess()->GetConnector(); |
| } |
| |
| } // namespace |
| |
| MediaRouterDesktop::~MediaRouterDesktop() = default; |
| |
| // static |
| void MediaRouterDesktop::BindToRequest(const extensions::Extension* extension, |
| content::BrowserContext* context, |
| mojom::MediaRouterRequest request, |
| content::RenderFrameHost* source) { |
| MediaRouterDesktop* impl = static_cast<MediaRouterDesktop*>( |
| MediaRouterFactory::GetApiForBrowserContext(context)); |
| DCHECK(impl); |
| |
| impl->BindToMojoRequest(std::move(request), *extension); |
| } |
| |
| void MediaRouterDesktop::OnUserGesture() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| MediaRouterMojoImpl::OnUserGesture(); |
| // Allow MRPM to intelligently update sinks and observers by passing in a |
| // media source. |
| UpdateMediaSinks(MediaSourceForDesktop().id()); |
| |
| media_sink_service_->OnUserGesture(); |
| |
| #if defined(OS_WIN) |
| EnsureMdnsDiscoveryEnabled(); |
| #endif |
| } |
| |
| base::Value MediaRouterDesktop::GetState() const { |
| return media_sink_service_status_.GetStatusAsValue(); |
| } |
| |
| base::Optional<MediaRouteProviderId> |
| MediaRouterDesktop::GetProviderIdForPresentation( |
| const std::string& presentation_id) { |
| // TODO(takumif): Once the Android Media Router also uses MediaRouterMojoImpl, |
| // we must support these presentation IDs in Android as well. |
| if (presentation_id == kAutoJoinPresentationId || |
| base::StartsWith(presentation_id, kCastPresentationIdPrefix, |
| base::CompareCase::SENSITIVE)) { |
| return MediaRouteProviderId::EXTENSION; |
| } |
| return MediaRouterMojoImpl::GetProviderIdForPresentation(presentation_id); |
| } |
| |
| MediaRouterDesktop::MediaRouterDesktop(content::BrowserContext* context) |
| : MediaRouterDesktop(context, DualMediaSinkService::GetInstance()) { |
| #if defined(OS_WIN) |
| CanFirewallUseLocalPorts( |
| base::BindOnce(&MediaRouterDesktop::OnFirewallCheckComplete, |
| weak_factory_.GetWeakPtr())); |
| #endif |
| } |
| |
| MediaRouterDesktop::MediaRouterDesktop(content::BrowserContext* context, |
| DualMediaSinkService* media_sink_service) |
| : MediaRouterMojoImpl(context), |
| cast_provider_(nullptr, base::OnTaskRunnerDeleter(nullptr)), |
| dial_provider_(nullptr, base::OnTaskRunnerDeleter(nullptr)), |
| media_sink_service_(media_sink_service), |
| weak_factory_(this) { |
| InitializeMediaRouteProviders(); |
| } |
| |
| void MediaRouterDesktop::RegisterMediaRouteProvider( |
| MediaRouteProviderId provider_id, |
| mojom::MediaRouteProviderPtr media_route_provider_ptr, |
| mojom::MediaRouter::RegisterMediaRouteProviderCallback callback) { |
| auto config = mojom::MediaRouteProviderConfig::New(); |
| // Enabling browser side discovery / sink query means disabling extension side |
| // discovery / sink query. We are migrating discovery from the external Media |
| // Route Provider to the Media Router (https://crbug.com/687383), so we need |
| // to disable it in the provider. |
| config->enable_cast_discovery = !CastDiscoveryEnabled(); |
| config->enable_dial_sink_query = !DialMediaRouteProviderEnabled(); |
| config->enable_cast_sink_query = !CastMediaRouteProviderEnabled(); |
| config->use_views_dialog = ShouldUseViewsDialog(); |
| config->use_mirroring_service = ShouldUseMirroringService(); |
| std::move(callback).Run(instance_id(), std::move(config)); |
| |
| SyncStateToMediaRouteProvider(provider_id); |
| |
| if (provider_id == MediaRouteProviderId::EXTENSION) { |
| RegisterExtensionMediaRouteProvider(std::move(media_route_provider_ptr)); |
| } else { |
| media_route_provider_ptr.set_connection_error_handler( |
| base::BindOnce(&MediaRouterDesktop::OnProviderConnectionError, |
| weak_factory_.GetWeakPtr(), provider_id)); |
| media_route_providers_[provider_id] = std::move(media_route_provider_ptr); |
| } |
| } |
| |
| void MediaRouterDesktop::OnSinksReceived( |
| MediaRouteProviderId provider_id, |
| const std::string& media_source, |
| const std::vector<MediaSinkInternal>& internal_sinks, |
| const std::vector<url::Origin>& origins) { |
| media_sink_service_status_.UpdateAvailableSinks(provider_id, media_source, |
| internal_sinks); |
| MediaRouterMojoImpl::OnSinksReceived(provider_id, media_source, |
| internal_sinks, origins); |
| } |
| |
| void MediaRouterDesktop::GetMediaSinkServiceStatus( |
| mojom::MediaRouter::GetMediaSinkServiceStatusCallback callback) { |
| std::move(callback).Run(media_sink_service_status_.GetStatusAsJSONString()); |
| } |
| |
| void MediaRouterDesktop::RegisterExtensionMediaRouteProvider( |
| mojom::MediaRouteProviderPtr extension_provider_ptr) { |
| ProvideSinksToExtension(); |
| #if defined(OS_WIN) |
| // The extension MRP already turns on mDNS discovery for platforms other than |
| // Windows. It only relies on this signalling from MR on Windows to avoid |
| // triggering a firewall prompt out of the context of MR from the user's |
| // perspective. This particular call reminds the extension to enable mDNS |
| // discovery when it wakes up, has been upgraded, etc. |
| if (should_enable_mdns_discovery_) |
| EnsureMdnsDiscoveryEnabled(); |
| #endif |
| // Now that we have a Mojo pointer to the extension MRP, we reset the Mojo |
| // pointers to extension-side route controllers and request them to be bound |
| // to new implementations. This must happen before EventPageRequestManager |
| // executes commands to the MRP and its route controllers. Commands to the |
| // route controllers, once executed, will be queued in Mojo pipes until the |
| // Mojo requests are bound to implementations. |
| // TODO(takumif): Once we have route controllers for MRPs other than the |
| // extension MRP, we'll need to group them by MRP so that below is performed |
| // only for extension route controllers. |
| for (const auto& pair : route_controllers_) |
| InitMediaRouteController(pair.second); |
| extension_provider_proxy_->RegisterMediaRouteProvider( |
| std::move(extension_provider_ptr)); |
| } |
| |
| void MediaRouterDesktop::BindToMojoRequest( |
| mojo::InterfaceRequest<mojom::MediaRouter> request, |
| const extensions::Extension& extension) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| MediaRouterMojoImpl::BindToMojoRequest(std::move(request)); |
| extension_provider_proxy_->SetExtensionId(extension.id()); |
| if (!provider_version_was_recorded_) { |
| MediaRouterMojoMetrics::RecordMediaRouteProviderVersion(extension); |
| provider_version_was_recorded_ = true; |
| } |
| } |
| |
| void MediaRouterDesktop::ProvideSinksToExtension() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DVLOG(1) << "ProvideSinksToExtension"; |
| // If calling |ProvideSinksToExtension| for the first time, add a callback to |
| // be notified of sink updates. |
| if (!media_sink_service_subscription_) { |
| media_sink_service_subscription_ = |
| media_sink_service_->AddSinksDiscoveredCallback(base::BindRepeating( |
| &MediaRouterDesktop::ProvideSinks, base::Unretained(this))); |
| } |
| |
| // Sync the current list of sinks to the extension. |
| for (const auto& provider_and_sinks : media_sink_service_->current_sinks()) |
| ProvideSinks(provider_and_sinks.first, provider_and_sinks.second); |
| } |
| |
| void MediaRouterDesktop::ProvideSinks( |
| const std::string& provider_name, |
| const std::vector<MediaSinkInternal>& sinks) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DVLOG(1) << "Provider [" << provider_name << "] found " << sinks.size() |
| << " devices..."; |
| media_route_providers_[MediaRouteProviderId::EXTENSION]->ProvideSinks( |
| provider_name, sinks); |
| |
| media_sink_service_status_.UpdateDiscoveredSinks(provider_name, sinks); |
| } |
| |
| void MediaRouterDesktop::InitializeMediaRouteProviders() { |
| InitializeExtensionMediaRouteProviderProxy(); |
| if (base::FeatureList::IsEnabled(features::kLocalScreenCasting)) |
| InitializeWiredDisplayMediaRouteProvider(); |
| if (CastMediaRouteProviderEnabled()) |
| InitializeCastMediaRouteProvider(); |
| if (DialMediaRouteProviderEnabled()) |
| InitializeDialMediaRouteProvider(); |
| } |
| |
| void MediaRouterDesktop::InitializeExtensionMediaRouteProviderProxy() { |
| if (!extension_provider_proxy_) { |
| extension_provider_proxy_ = |
| std::make_unique<ExtensionMediaRouteProviderProxy>(context()); |
| } |
| mojom::MediaRouteProviderPtr extension_provider_proxy_ptr; |
| extension_provider_proxy_->Bind( |
| mojo::MakeRequest(&extension_provider_proxy_ptr)); |
| extension_provider_proxy_ptr.set_connection_error_handler(base::BindOnce( |
| &MediaRouterDesktop::OnExtensionProviderError, base::Unretained(this))); |
| media_route_providers_[MediaRouteProviderId::EXTENSION] = |
| std::move(extension_provider_proxy_ptr); |
| } |
| |
| void MediaRouterDesktop::OnExtensionProviderError() { |
| // The message pipe for |extension_provider_proxy_| might error out due to |
| // Media Router extension causing dropped callbacks. Detect this case and |
| // recover by re-creating the pipe. |
| DVLOG(2) << "Extension MRP encountered error."; |
| if (extension_provider_error_count_ >= kMaxMediaRouteProviderErrorCount) |
| return; |
| |
| ++extension_provider_error_count_; |
| DVLOG(2) << "Reconnecting to extension MRP: " |
| << extension_provider_error_count_; |
| InitializeExtensionMediaRouteProviderProxy(); |
| } |
| |
| void MediaRouterDesktop::InitializeWiredDisplayMediaRouteProvider() { |
| mojom::MediaRouterPtr media_router_ptr; |
| MediaRouterMojoImpl::BindToMojoRequest(mojo::MakeRequest(&media_router_ptr)); |
| mojom::MediaRouteProviderPtr wired_display_provider_ptr; |
| wired_display_provider_ = std::make_unique<WiredDisplayMediaRouteProvider>( |
| mojo::MakeRequest(&wired_display_provider_ptr), |
| std::move(media_router_ptr), Profile::FromBrowserContext(context())); |
| RegisterMediaRouteProvider(MediaRouteProviderId::WIRED_DISPLAY, |
| std::move(wired_display_provider_ptr), |
| base::DoNothing()); |
| } |
| |
| std::string MediaRouterDesktop::GetHashToken() { |
| return GetReceiverIdHashToken( |
| Profile::FromBrowserContext(context())->GetPrefs()); |
| } |
| |
| void MediaRouterDesktop::InitializeCastMediaRouteProvider() { |
| auto task_runner = |
| cast_channel::CastSocketService::GetInstance()->task_runner(); |
| mojom::MediaRouterPtr media_router_ptr; |
| MediaRouterMojoImpl::BindToMojoRequest(mojo::MakeRequest(&media_router_ptr)); |
| mojom::MediaRouteProviderPtr cast_provider_ptr; |
| cast_provider_ = |
| std::unique_ptr<CastMediaRouteProvider, base::OnTaskRunnerDeleter>( |
| new CastMediaRouteProvider( |
| mojo::MakeRequest(&cast_provider_ptr), |
| media_router_ptr.PassInterface(), |
| media_sink_service_->GetCastMediaSinkServiceImpl(), |
| media_sink_service_->cast_app_discovery_service(), |
| GetCastMessageHandler(), GetConnector(), GetHashToken(), |
| task_runner), |
| base::OnTaskRunnerDeleter(task_runner)); |
| RegisterMediaRouteProvider(MediaRouteProviderId::CAST, |
| std::move(cast_provider_ptr), base::DoNothing()); |
| } |
| |
| void MediaRouterDesktop::InitializeDialMediaRouteProvider() { |
| mojom::MediaRouterPtr media_router_ptr; |
| MediaRouterMojoImpl::BindToMojoRequest(mojo::MakeRequest(&media_router_ptr)); |
| mojom::MediaRouteProviderPtr dial_provider_ptr; |
| |
| auto* dial_media_sink_service = |
| media_sink_service_->GetDialMediaSinkServiceImpl(); |
| auto task_runner = dial_media_sink_service->task_runner(); |
| |
| dial_provider_ = |
| std::unique_ptr<DialMediaRouteProvider, base::OnTaskRunnerDeleter>( |
| new DialMediaRouteProvider(mojo::MakeRequest(&dial_provider_ptr), |
| media_router_ptr.PassInterface(), |
| dial_media_sink_service, GetConnector(), |
| GetHashToken(), task_runner), |
| base::OnTaskRunnerDeleter(task_runner)); |
| RegisterMediaRouteProvider(MediaRouteProviderId::DIAL, |
| std::move(dial_provider_ptr), base::DoNothing()); |
| } |
| |
| #if defined(OS_WIN) |
| void MediaRouterDesktop::EnsureMdnsDiscoveryEnabled() { |
| if (CastDiscoveryEnabled()) { |
| media_sink_service_->StartMdnsDiscovery(); |
| } else { |
| media_route_providers_[MediaRouteProviderId::EXTENSION] |
| ->EnableMdnsDiscovery(); |
| } |
| |
| // Record that we enabled mDNS discovery, so that we will know to enable again |
| // when we reconnect to the component extension. |
| should_enable_mdns_discovery_ = true; |
| } |
| |
| void MediaRouterDesktop::OnFirewallCheckComplete( |
| bool firewall_can_use_local_ports) { |
| if (firewall_can_use_local_ports) |
| EnsureMdnsDiscoveryEnabled(); |
| } |
| #endif |
| |
| } // namespace media_router |