blob: 961b958efbed8fbbefb4d44567d36f7148473fa1 [file] [log] [blame]
// Copyright 2015 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_mojo_impl.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_vector.h"
#include "base/observer_list.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/media/router/issues_observer.h"
#include "chrome/browser/media/router/media_router_factory.h"
#include "chrome/browser/media/router/media_routes_observer.h"
#include "chrome/browser/media/router/media_sinks_observer.h"
#include "chrome/browser/media/router/media_source_helper.h"
#include "chrome/browser/media/router/mojo/media_route_provider_util_win.h"
#include "chrome/browser/media/router/mojo/media_router_mojo_metrics.h"
#include "chrome/browser/media/router/mojo/media_router_type_converters.h"
#include "chrome/browser/media/router/presentation_session_messages_observer.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/process_manager.h"
#define DVLOG_WITH_INSTANCE(level) \
DVLOG(level) << "MR #" << instance_id_ << ": "
#define DLOG_WITH_INSTANCE(level) DLOG(level) << "MR #" << instance_id_ << ": "
namespace media_router {
using SinkAvailability = interfaces::MediaRouter::SinkAvailability;
namespace {
std::unique_ptr<content::PresentationSessionMessage>
ConvertToPresentationSessionMessage(interfaces::RouteMessagePtr input) {
DCHECK(!input.is_null());
std::unique_ptr<content::PresentationSessionMessage> output;
switch (input->type) {
case interfaces::RouteMessage::Type::TEXT: {
DCHECK(input->message);
DCHECK(!input->data);
output.reset(new content::PresentationSessionMessage(
content::PresentationMessageType::TEXT));
output->message = std::move(*input->message);
return output;
}
case interfaces::RouteMessage::Type::BINARY: {
DCHECK(input->data);
DCHECK(!input->message);
output.reset(new content::PresentationSessionMessage(
content::PresentationMessageType::ARRAY_BUFFER));
output->data.reset(new std::vector<uint8_t>(std::move(*input->data)));
return output;
}
}
NOTREACHED() << "Invalid route message type " << input->type;
return output;
}
} // namespace
MediaRouterMojoImpl::MediaRoutesQuery::MediaRoutesQuery() = default;
MediaRouterMojoImpl::MediaRoutesQuery::~MediaRoutesQuery() = default;
MediaRouterMojoImpl::MediaSinksQuery::MediaSinksQuery() = default;
MediaRouterMojoImpl::MediaSinksQuery::~MediaSinksQuery() = default;
MediaRouterMojoImpl::MediaRouterMojoImpl(
extensions::EventPageTracker* event_page_tracker)
: event_page_tracker_(event_page_tracker),
instance_id_(base::GenerateGUID()),
availability_(interfaces::MediaRouter::SinkAvailability::UNAVAILABLE),
current_wake_reason_(MediaRouteProviderWakeReason::TOTAL_COUNT),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(event_page_tracker_);
#if defined(OS_WIN)
CanFirewallUseLocalPorts(
base::Bind(&MediaRouterMojoImpl::OnFirewallCheckComplete,
weak_factory_.GetWeakPtr()));
#endif
}
MediaRouterMojoImpl::~MediaRouterMojoImpl() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
// static
void MediaRouterMojoImpl::BindToRequest(
const extensions::Extension* extension,
content::BrowserContext* context,
mojo::InterfaceRequest<interfaces::MediaRouter> request) {
MediaRouterMojoImpl* impl = static_cast<MediaRouterMojoImpl*>(
MediaRouterFactory::GetApiForBrowserContext(context));
DCHECK(impl);
impl->BindToMojoRequest(std::move(request), *extension);
}
void MediaRouterMojoImpl::BindToMojoRequest(
mojo::InterfaceRequest<interfaces::MediaRouter> request,
const extensions::Extension& extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
binding_.reset(
new mojo::Binding<interfaces::MediaRouter>(this, std::move(request)));
binding_->set_connection_error_handler(base::Bind(
&MediaRouterMojoImpl::OnConnectionError, base::Unretained(this)));
media_route_provider_extension_id_ = extension.id();
if (!provider_version_was_recorded_) {
MediaRouterMojoMetrics::RecordMediaRouteProviderVersion(extension);
provider_version_was_recorded_ = true;
}
}
void MediaRouterMojoImpl::OnConnectionError() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
media_route_provider_.reset();
binding_.reset();
// If |OnConnectionError| is invoked while there are pending requests, then
// it means we tried to wake the extension, but weren't able to complete the
// connection to media route provider. Since we do not know whether the error
// is transient, reattempt the wakeup.
if (!pending_requests_.empty()) {
DLOG_WITH_INSTANCE(ERROR) << "A connection error while there are pending "
"requests.";
SetWakeReason(MediaRouteProviderWakeReason::CONNECTION_ERROR);
AttemptWakeEventPage();
}
}
void MediaRouterMojoImpl::RegisterMediaRouteProvider(
interfaces::MediaRouteProviderPtr media_route_provider_ptr,
const interfaces::MediaRouter::RegisterMediaRouteProviderCallback&
callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
#if defined(OS_WIN)
// The MRPM may have been upgraded or otherwise reload such that we could be
// seeing an MRPM that doesn't know mDNS is enabled, even if we've told a
// previously registered MRPM it should be enabled. Furthermore, there may be
// a pending request to enable mDNS, so don't clear this flag after
// ExecutePendingRequests().
is_mdns_enabled_ = false;
#endif
if (event_page_tracker_->IsEventPageSuspended(
media_route_provider_extension_id_)) {
DVLOG_WITH_INSTANCE(1)
<< "RegisterMediaRouteProvider was called while extension is "
"suspended.";
media_route_provider_.reset();
SetWakeReason(MediaRouteProviderWakeReason::REGISTER_MEDIA_ROUTE_PROVIDER);
AttemptWakeEventPage();
return;
}
media_route_provider_ = std::move(media_route_provider_ptr);
media_route_provider_.set_connection_error_handler(base::Bind(
&MediaRouterMojoImpl::OnConnectionError, base::Unretained(this)));
callback.Run(instance_id_);
ExecutePendingRequests();
wakeup_attempt_count_ = 0;
#if defined(OS_WIN)
// The MRPM extension 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_) {
DoEnsureMdnsDiscoveryEnabled();
}
#endif
}
void MediaRouterMojoImpl::OnIssue(const interfaces::IssuePtr issue) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG_WITH_INSTANCE(1) << "OnIssue " << issue->title;
const Issue& issue_converted = issue.To<Issue>();
issue_manager_.AddIssue(issue_converted);
}
void MediaRouterMojoImpl::OnSinksReceived(
const std::string& media_source,
std::vector<interfaces::MediaSinkPtr> sinks,
const std::vector<std::string>& origins) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG_WITH_INSTANCE(1) << "OnSinksReceived";
auto it = sinks_queries_.find(media_source);
if (it == sinks_queries_.end()) {
DVLOG_WITH_INSTANCE(1) << "Received sink list without MediaSinksQuery.";
return;
}
std::vector<GURL> origin_list;
origin_list.reserve(origins.size());
for (size_t i = 0; i < origins.size(); ++i) {
GURL origin(origins[i]);
if (!origin.is_valid()) {
LOG(WARNING) << "Received invalid origin: " << origin
<< ". Dropping result.";
return;
}
origin_list.push_back(origin);
}
std::vector<MediaSink> sink_list;
sink_list.reserve(sinks.size());
for (size_t i = 0; i < sinks.size(); ++i)
sink_list.push_back(sinks[i].To<MediaSink>());
auto* sinks_query = it->second;
sinks_query->has_cached_result = true;
sinks_query->origins.swap(origin_list);
sinks_query->cached_sink_list.swap(sink_list);
if (!sinks_query->observers.might_have_observers()) {
DVLOG_WITH_INSTANCE(1)
<< "Received sink list without any active observers: " << media_source;
} else {
FOR_EACH_OBSERVER(
MediaSinksObserver, sinks_query->observers,
OnSinksUpdated(sinks_query->cached_sink_list, sinks_query->origins));
}
}
void MediaRouterMojoImpl::OnRoutesUpdated(
std::vector<interfaces::MediaRoutePtr> routes,
const std::string& media_source,
const std::vector<std::string>& joinable_route_ids) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG_WITH_INSTANCE(1) << "OnRoutesUpdated";
auto it = routes_queries_.find(media_source);
if (it == routes_queries_.end() ||
!(it->second->observers.might_have_observers())) {
DVLOG_WITH_INSTANCE(1)
<< "Received route list without any active observers: " << media_source;
return;
}
std::vector<MediaRoute> routes_converted;
routes_converted.reserve(routes.size());
for (size_t i = 0; i < routes.size(); ++i)
routes_converted.push_back(routes[i].To<MediaRoute>());
FOR_EACH_OBSERVER(
MediaRoutesObserver, it->second->observers,
OnRoutesUpdated(routes_converted, joinable_route_ids));
}
void MediaRouterMojoImpl::RouteResponseReceived(
const std::string& presentation_id,
bool incognito,
const std::vector<MediaRouteResponseCallback>& callbacks,
interfaces::MediaRoutePtr media_route,
const base::Optional<std::string>& error_text,
interfaces::RouteRequestResultCode result_code) {
std::unique_ptr<RouteRequestResult> result;
if (media_route.is_null()) {
// An error occurred.
const std::string& error = (error_text && !error_text->empty())
? *error_text : std::string("Unknown error.");
result = RouteRequestResult::FromError(
error, mojo::RouteRequestResultCodeFromMojo(result_code));
} else if (media_route->incognito != incognito) {
std::string error = base::StringPrintf(
"Mismatch in incognito status: request = %d, response = %d", incognito,
media_route->incognito);
result = RouteRequestResult::FromError(
error, RouteRequestResult::INCOGNITO_MISMATCH);
} else {
result = RouteRequestResult::FromSuccess(
media_route.To<std::unique_ptr<MediaRoute>>(), presentation_id);
}
// TODO(imcheng): Add UMA histogram based on result code (crbug.com/583044).
for (const MediaRouteResponseCallback& callback : callbacks)
callback.Run(*result);
}
void MediaRouterMojoImpl::CreateRoute(
const MediaSource::Id& source_id,
const MediaSink::Id& sink_id,
const GURL& origin,
content::WebContents* web_contents,
const std::vector<MediaRouteResponseCallback>& callbacks,
base::TimeDelta timeout,
bool incognito) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!origin.is_valid()) {
DVLOG_WITH_INSTANCE(1) << "Invalid origin: " << origin;
std::unique_ptr<RouteRequestResult> error_result(
RouteRequestResult::FromError("Invalid origin",
RouteRequestResult::INVALID_ORIGIN));
for (const MediaRouteResponseCallback& callback : callbacks)
callback.Run(*error_result);
return;
}
SetWakeReason(MediaRouteProviderWakeReason::CREATE_ROUTE);
int tab_id = SessionTabHelper::IdForTab(web_contents);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoCreateRoute,
base::Unretained(this), source_id, sink_id,
origin.is_empty() ? "" : origin.spec(), tab_id,
callbacks, timeout, incognito));
}
void MediaRouterMojoImpl::JoinRoute(
const MediaSource::Id& source_id,
const std::string& presentation_id,
const GURL& origin,
content::WebContents* web_contents,
const std::vector<MediaRouteResponseCallback>& callbacks,
base::TimeDelta timeout,
bool incognito) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<RouteRequestResult> error_result;
if (!origin.is_valid()) {
DVLOG_WITH_INSTANCE(1) << "Invalid origin: " << origin;
error_result = RouteRequestResult::FromError(
"Invalid origin", RouteRequestResult::INVALID_ORIGIN);
} else if (!HasJoinableRoute()) {
DVLOG_WITH_INSTANCE(1) << "No joinable routes";
error_result = RouteRequestResult::FromError(
"Route not found", RouteRequestResult::ROUTE_NOT_FOUND);
}
if (error_result) {
for (const MediaRouteResponseCallback& callback : callbacks)
callback.Run(*error_result);
return;
}
SetWakeReason(MediaRouteProviderWakeReason::JOIN_ROUTE);
int tab_id = SessionTabHelper::IdForTab(web_contents);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoJoinRoute,
base::Unretained(this), source_id, presentation_id,
origin.is_empty() ? "" : origin.spec(), tab_id,
callbacks, timeout, incognito));
}
void MediaRouterMojoImpl::ConnectRouteByRouteId(
const MediaSource::Id& source_id,
const MediaRoute::Id& route_id,
const GURL& origin,
content::WebContents* web_contents,
const std::vector<MediaRouteResponseCallback>& callbacks,
base::TimeDelta timeout,
bool incognito) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!origin.is_valid()) {
DVLOG_WITH_INSTANCE(1) << "Invalid origin: " << origin;
std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
"Invalid origin", RouteRequestResult::INVALID_ORIGIN);
for (const MediaRouteResponseCallback& callback : callbacks)
callback.Run(*result);
return;
}
SetWakeReason(MediaRouteProviderWakeReason::CONNECT_ROUTE_BY_ROUTE_ID);
int tab_id = SessionTabHelper::IdForTab(web_contents);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoConnectRouteByRouteId,
base::Unretained(this), source_id, route_id,
origin.is_empty() ? "" : origin.spec(), tab_id,
callbacks, timeout, incognito));
}
void MediaRouterMojoImpl::TerminateRoute(const MediaRoute::Id& route_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG(2) << "TerminateRoute " << route_id;
SetWakeReason(MediaRouteProviderWakeReason::TERMINATE_ROUTE);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoTerminateRoute,
base::Unretained(this), route_id));
}
void MediaRouterMojoImpl::DetachRoute(const MediaRoute::Id& route_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetWakeReason(MediaRouteProviderWakeReason::DETACH_ROUTE);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoDetachRoute,
base::Unretained(this), route_id));
}
void MediaRouterMojoImpl::SendRouteMessage(
const MediaRoute::Id& route_id,
const std::string& message,
const SendRouteMessageCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetWakeReason(MediaRouteProviderWakeReason::SEND_SESSION_MESSAGE);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoSendSessionMessage,
base::Unretained(this), route_id, message, callback));
}
void MediaRouterMojoImpl::SendRouteBinaryMessage(
const MediaRoute::Id& route_id,
std::unique_ptr<std::vector<uint8_t>> data,
const SendRouteMessageCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetWakeReason(MediaRouteProviderWakeReason::SEND_SESSION_BINARY_MESSAGE);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoSendSessionBinaryMessage,
base::Unretained(this), route_id,
base::Passed(std::move(data)), callback));
}
void MediaRouterMojoImpl::AddIssue(const Issue& issue) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
issue_manager_.AddIssue(issue);
}
void MediaRouterMojoImpl::ClearIssue(const Issue::Id& issue_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
issue_manager_.ClearIssue(issue_id);
}
void MediaRouterMojoImpl::OnUserGesture() {
// Allow MRPM to intelligently update sinks and observers by passing in a
// media source.
UpdateMediaSinks(MediaSourceForDesktop().id());
#if defined(OS_WIN)
EnsureMdnsDiscoveryEnabled();
#endif
}
void MediaRouterMojoImpl::SearchSinks(
const MediaSink::Id& sink_id,
const MediaSource::Id& source_id,
const std::string& search_input,
const std::string& domain,
const MediaSinkSearchResponseCallback& sink_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetWakeReason(MediaRouteProviderWakeReason::SEARCH_SINKS);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoSearchSinks,
base::Unretained(this), sink_id, source_id,
search_input, domain, sink_callback));
}
bool MediaRouterMojoImpl::RegisterMediaSinksObserver(
MediaSinksObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Create an observer list for the media source and add |observer|
// to it. Fail if |observer| is already registered.
const std::string& source_id = observer->source().id();
auto* sinks_query = sinks_queries_.get(source_id);
bool new_query = false;
if (!sinks_query) {
new_query = true;
sinks_query = new MediaSinksQuery;
sinks_queries_.add(source_id, base::WrapUnique(sinks_query));
} else {
DCHECK(!sinks_query->observers.HasObserver(observer));
}
// If sink availability is UNAVAILABLE, then there is no need to call MRPM.
// |observer| can be immediately notified with an empty list.
sinks_query->observers.AddObserver(observer);
if (availability_ == interfaces::MediaRouter::SinkAvailability::UNAVAILABLE) {
observer->OnSinksUpdated(std::vector<MediaSink>(), std::vector<GURL>());
} else {
// Need to call MRPM to start observing sinks if the query is new.
if (new_query) {
SetWakeReason(MediaRouteProviderWakeReason::START_OBSERVING_MEDIA_SINKS);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStartObservingMediaSinks,
base::Unretained(this), source_id));
} else if (sinks_query->has_cached_result) {
observer->OnSinksUpdated(sinks_query->cached_sink_list,
sinks_query->origins);
}
}
return true;
}
void MediaRouterMojoImpl::UnregisterMediaSinksObserver(
MediaSinksObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const MediaSource::Id& source_id = observer->source().id();
auto* sinks_query = sinks_queries_.get(source_id);
if (!sinks_query || !sinks_query->observers.HasObserver(observer)) {
return;
}
// If we are removing the final observer for the source, then stop
// observing sinks for it.
// might_have_observers() is reliable here on the assumption that this call
// is not inside the ObserverList iteration.
sinks_query->observers.RemoveObserver(observer);
if (!sinks_query->observers.might_have_observers()) {
// Only ask MRPM to stop observing media sinks if the availability is not
// UNAVAILABLE.
// Otherwise, the MRPM would have discarded the queries already.
if (availability_ !=
interfaces::MediaRouter::SinkAvailability::UNAVAILABLE) {
SetWakeReason(MediaRouteProviderWakeReason::STOP_OBSERVING_MEDIA_SINKS);
// The |sinks_queries_| entry will be removed in the immediate or deferred
// |DoStopObservingMediaSinks| call.
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStopObservingMediaSinks,
base::Unretained(this), source_id));
} else {
sinks_queries_.erase(source_id);
}
}
}
void MediaRouterMojoImpl::RegisterMediaRoutesObserver(
MediaRoutesObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const MediaSource::Id source_id = observer->source_id();
auto* routes_query = routes_queries_.get(source_id);
if (!routes_query) {
routes_query = new MediaRoutesQuery;
routes_queries_.add(source_id, base::WrapUnique(routes_query));
} else {
DCHECK(!routes_query->observers.HasObserver(observer));
}
routes_query->observers.AddObserver(observer);
SetWakeReason(MediaRouteProviderWakeReason::START_OBSERVING_MEDIA_ROUTES);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStartObservingMediaRoutes,
base::Unretained(this), source_id));
}
void MediaRouterMojoImpl::UnregisterMediaRoutesObserver(
MediaRoutesObserver* observer) {
const MediaSource::Id source_id = observer->source_id();
auto* routes_query = routes_queries_.get(source_id);
if (!routes_query || !routes_query->observers.HasObserver(observer)) {
return;
}
// If we are removing the final observer for the source, then stop
// observing routes for it.
// might_have_observers() is reliable here on the assumption that this call
// is not inside the ObserverList iteration.
routes_query->observers.RemoveObserver(observer);
if (!routes_query->observers.might_have_observers()) {
SetWakeReason(MediaRouteProviderWakeReason::STOP_OBSERVING_MEDIA_ROUTES);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStopObservingMediaRoutes,
base::Unretained(this), source_id));
}
}
void MediaRouterMojoImpl::RegisterIssuesObserver(IssuesObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
issue_manager_.RegisterObserver(observer);
}
void MediaRouterMojoImpl::UnregisterIssuesObserver(IssuesObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
issue_manager_.UnregisterObserver(observer);
}
void MediaRouterMojoImpl::RegisterPresentationSessionMessagesObserver(
PresentationSessionMessagesObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(observer);
const MediaRoute::Id& route_id = observer->route_id();
auto* observer_list = messages_observers_.get(route_id);
if (!observer_list) {
observer_list = new base::ObserverList<PresentationSessionMessagesObserver>;
messages_observers_.add(route_id, base::WrapUnique(observer_list));
} else {
DCHECK(!observer_list->HasObserver(observer));
}
bool should_listen = !observer_list->might_have_observers();
observer_list->AddObserver(observer);
if (should_listen) {
SetWakeReason(
MediaRouteProviderWakeReason::START_LISTENING_FOR_ROUTE_MESSAGES);
RunOrDefer(
base::Bind(&MediaRouterMojoImpl::DoStartListeningForRouteMessages,
base::Unretained(this), route_id));
}
}
void MediaRouterMojoImpl::UnregisterPresentationSessionMessagesObserver(
PresentationSessionMessagesObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(observer);
const MediaRoute::Id& route_id = observer->route_id();
auto* observer_list = messages_observers_.get(route_id);
if (!observer_list || !observer_list->HasObserver(observer))
return;
observer_list->RemoveObserver(observer);
if (!observer_list->might_have_observers()) {
messages_observers_.erase(route_id);
SetWakeReason(
MediaRouteProviderWakeReason::STOP_LISTENING_FOR_ROUTE_MESSAGES);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStopListeningForRouteMessages,
base::Unretained(this), route_id));
}
}
void MediaRouterMojoImpl::DoCreateRoute(
const MediaSource::Id& source_id,
const MediaSink::Id& sink_id,
const std::string& origin,
int tab_id,
const std::vector<MediaRouteResponseCallback>& callbacks,
base::TimeDelta timeout,
bool incognito) {
std::string presentation_id = MediaRouterBase::CreatePresentationId();
DVLOG_WITH_INSTANCE(1) << "DoCreateRoute " << source_id << "=>" << sink_id
<< ", presentation ID: " << presentation_id;
media_route_provider_->CreateRoute(
source_id, sink_id, presentation_id, origin, tab_id,
timeout > base::TimeDelta() ? timeout.InMilliseconds() : 0, incognito,
base::Bind(&MediaRouterMojoImpl::RouteResponseReceived,
base::Unretained(this), presentation_id, incognito,
callbacks));
}
void MediaRouterMojoImpl::DoJoinRoute(
const MediaSource::Id& source_id,
const std::string& presentation_id,
const std::string& origin,
int tab_id,
const std::vector<MediaRouteResponseCallback>& callbacks,
base::TimeDelta timeout,
bool incognito) {
DVLOG_WITH_INSTANCE(1) << "DoJoinRoute " << source_id
<< ", presentation ID: " << presentation_id;
media_route_provider_->JoinRoute(
source_id, presentation_id, origin, tab_id,
timeout > base::TimeDelta() ? timeout.InMilliseconds() : 0, incognito,
base::Bind(&MediaRouterMojoImpl::RouteResponseReceived,
base::Unretained(this), presentation_id, incognito,
callbacks));
}
void MediaRouterMojoImpl::DoConnectRouteByRouteId(
const MediaSource::Id& source_id,
const MediaRoute::Id& route_id,
const std::string& origin,
int tab_id,
const std::vector<MediaRouteResponseCallback>& callbacks,
base::TimeDelta timeout,
bool incognito) {
std::string presentation_id = MediaRouterBase::CreatePresentationId();
DVLOG_WITH_INSTANCE(1) << "DoConnectRouteByRouteId " << source_id
<< ", route ID: " << route_id
<< ", presentation ID: " << presentation_id;
media_route_provider_->ConnectRouteByRouteId(
source_id, route_id, presentation_id, origin, tab_id,
timeout > base::TimeDelta() ? timeout.InMilliseconds() : 0, incognito,
base::Bind(&MediaRouterMojoImpl::RouteResponseReceived,
base::Unretained(this), presentation_id, incognito,
callbacks));
}
void MediaRouterMojoImpl::DoTerminateRoute(const MediaRoute::Id& route_id) {
DVLOG_WITH_INSTANCE(1) << "DoTerminateRoute " << route_id;
media_route_provider_->TerminateRoute(
route_id,
base::Bind(&MediaRouterMojoImpl::OnTerminateRouteResult,
base::Unretained(this), route_id));
}
void MediaRouterMojoImpl::DoDetachRoute(const MediaRoute::Id& route_id) {
DVLOG_WITH_INSTANCE(1) << "DoDetachRoute " << route_id;
media_route_provider_->DetachRoute(route_id);
}
void MediaRouterMojoImpl::DoSendSessionMessage(
const MediaRoute::Id& route_id,
const std::string& message,
const SendRouteMessageCallback& callback) {
DVLOG_WITH_INSTANCE(1) << "SendRouteMessage " << route_id;
media_route_provider_->SendRouteMessage(route_id, message, callback);
}
void MediaRouterMojoImpl::DoSendSessionBinaryMessage(
const MediaRoute::Id& route_id,
std::unique_ptr<std::vector<uint8_t>> data,
const SendRouteMessageCallback& callback) {
DVLOG_WITH_INSTANCE(1) << "SendRouteBinaryMessage " << route_id;
media_route_provider_->SendRouteBinaryMessage(route_id, *data, callback);
}
void MediaRouterMojoImpl::DoStartListeningForRouteMessages(
const MediaRoute::Id& route_id) {
DVLOG_WITH_INSTANCE(1) << "DoStartListeningForRouteMessages";
media_route_provider_->StartListeningForRouteMessages(route_id);
}
void MediaRouterMojoImpl::DoStopListeningForRouteMessages(
const MediaRoute::Id& route_id) {
DVLOG_WITH_INSTANCE(1) << "StopListeningForRouteMessages";
media_route_provider_->StopListeningForRouteMessages(route_id);
}
void MediaRouterMojoImpl::DoSearchSinks(
const MediaSink::Id& sink_id,
const MediaSource::Id& source_id,
const std::string& search_input,
const std::string& domain,
const MediaSinkSearchResponseCallback& sink_callback) {
DVLOG_WITH_INSTANCE(1) << "SearchSinks";
auto sink_search_criteria = interfaces::SinkSearchCriteria::New();
sink_search_criteria->input = search_input;
sink_search_criteria->domain = domain;
media_route_provider_->SearchSinks(
sink_id, source_id, std::move(sink_search_criteria), sink_callback);
}
void MediaRouterMojoImpl::OnRouteMessagesReceived(
const std::string& route_id,
std::vector<interfaces::RouteMessagePtr> messages) {
DVLOG_WITH_INSTANCE(1) << "OnRouteMessagesReceived";
DCHECK(!messages.empty());
auto* observer_list = messages_observers_.get(route_id);
if (!observer_list) {
return;
}
ScopedVector<content::PresentationSessionMessage> session_messages;
session_messages.reserve(messages.size());
for (size_t i = 0; i < messages.size(); ++i) {
session_messages.push_back(
ConvertToPresentationSessionMessage(std::move(messages[i])));
}
base::ObserverList<PresentationSessionMessagesObserver>::Iterator observer_it(
observer_list);
bool single_observer =
observer_it.GetNext() != nullptr && observer_it.GetNext() == nullptr;
FOR_EACH_OBSERVER(PresentationSessionMessagesObserver, *observer_list,
OnMessagesReceived(session_messages, single_observer));
}
void MediaRouterMojoImpl::OnSinkAvailabilityUpdated(
SinkAvailability availability) {
if (availability_ == availability)
return;
availability_ = availability;
if (availability_ == interfaces::MediaRouter::SinkAvailability::UNAVAILABLE) {
// Sinks are no longer available. MRPM has already removed all sink queries.
for (auto& source_and_query : sinks_queries_) {
auto* query = source_and_query.second;
query->is_active = false;
query->has_cached_result = false;
query->cached_sink_list.clear();
query->origins.clear();
}
} else {
// Sinks are now available. Tell MRPM to start all sink queries again.
for (const auto& source_and_query : sinks_queries_) {
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoStartObservingMediaSinks,
base::Unretained(this), source_and_query.first));
}
}
}
void MediaRouterMojoImpl::OnPresentationConnectionStateChanged(
const std::string& route_id,
interfaces::MediaRouter::PresentationConnectionState state) {
NotifyPresentationConnectionStateChange(
route_id, mojo::PresentationConnectionStateFromMojo(state));
}
void MediaRouterMojoImpl::OnPresentationConnectionClosed(
const std::string& route_id,
interfaces::MediaRouter::PresentationConnectionCloseReason reason,
const std::string& message) {
NotifyPresentationConnectionClose(
route_id, mojo::PresentationConnectionCloseReasonFromMojo(reason),
message);
}
void MediaRouterMojoImpl::OnTerminateRouteResult(
const MediaRoute::Id& route_id,
const base::Optional<std::string>& error_text,
interfaces::RouteRequestResultCode result_code) {
if (result_code != interfaces::RouteRequestResultCode::OK) {
LOG(WARNING) << "Failed to terminate route " << route_id
<< ": result_code = " << result_code << ", "
<< error_text.value_or(std::string());
}
MediaRouterMojoMetrics::RecordMediaRouteProviderTerminateRoute(
mojo::RouteRequestResultCodeFromMojo(result_code));
}
void MediaRouterMojoImpl::DoStartObservingMediaSinks(
const MediaSource::Id& source_id) {
DVLOG_WITH_INSTANCE(1) << "DoStartObservingMediaSinks: " << source_id;
// No need to call MRPM if there are no sinks available.
if (availability_ == interfaces::MediaRouter::SinkAvailability::UNAVAILABLE)
return;
// No need to call MRPM if all observers have been removed in the meantime.
auto* sinks_query = sinks_queries_.get(source_id);
if (!sinks_query || !sinks_query->observers.might_have_observers())
return;
DVLOG_WITH_INSTANCE(1) << "MRPM.StartObservingMediaSinks: " << source_id;
media_route_provider_->StartObservingMediaSinks(source_id);
sinks_query->is_active = true;
}
void MediaRouterMojoImpl::DoStopObservingMediaSinks(
const MediaSource::Id& source_id) {
DVLOG_WITH_INSTANCE(1) << "DoStopObservingMediaSinks: " << source_id;
auto* sinks_query = sinks_queries_.get(source_id);
// No need to call MRPM if observers have been added in the meantime,
// or StopObservingMediaSinks has already been called.
if (!sinks_query || !sinks_query->is_active ||
sinks_query->observers.might_have_observers()) {
return;
}
DVLOG_WITH_INSTANCE(1) << "MRPM.StopObservingMediaSinks: " << source_id;
media_route_provider_->StopObservingMediaSinks(source_id);
sinks_queries_.erase(source_id);
}
void MediaRouterMojoImpl::DoStartObservingMediaRoutes(
const MediaSource::Id& source_id) {
DVLOG_WITH_INSTANCE(1) << "DoStartObservingMediaRoutes";
// No need to call MRPM if all observers have been removed in the meantime.
auto* routes_query = routes_queries_.get(source_id);
if (!routes_query || !routes_query->observers.might_have_observers())
return;
DVLOG_WITH_INSTANCE(1) << "MRPM.StartObservingMediaRoutes: " << source_id;
media_route_provider_->StartObservingMediaRoutes(source_id);
routes_query->is_active = true;
}
void MediaRouterMojoImpl::DoStopObservingMediaRoutes(
const MediaSource::Id& source_id) {
DVLOG_WITH_INSTANCE(1) << "DoStopObservingMediaRoutes";
// No need to call MRPM if observers have been added in the meantime,
// or StopObservingMediaRoutes has already been called.
auto* routes_query = routes_queries_.get(source_id);
if (!routes_query || !routes_query->is_active ||
routes_query->observers.might_have_observers()) {
return;
}
DVLOG_WITH_INSTANCE(1) << "MRPM.StopObservingMediaRoutes: " << source_id;
media_route_provider_->StopObservingMediaRoutes(source_id);
routes_queries_.erase(source_id);
}
void MediaRouterMojoImpl::EnqueueTask(const base::Closure& closure) {
pending_requests_.push_back(closure);
if (pending_requests_.size() > kMaxPendingRequests) {
DLOG_WITH_INSTANCE(ERROR) << "Reached max queue size. Dropping oldest "
<< "request.";
pending_requests_.pop_front();
}
DVLOG_WITH_INSTANCE(2) << "EnqueueTask (queue-length="
<< pending_requests_.size() << ")";
}
void MediaRouterMojoImpl::RunOrDefer(const base::Closure& request) {
DCHECK(event_page_tracker_);
if (media_route_provider_extension_id_.empty()) {
DVLOG_WITH_INSTANCE(1) << "Extension ID not known yet.";
EnqueueTask(request);
} else if (event_page_tracker_->IsEventPageSuspended(
media_route_provider_extension_id_)) {
DVLOG_WITH_INSTANCE(1) << "Waking event page.";
EnqueueTask(request);
AttemptWakeEventPage();
media_route_provider_.reset();
} else if (!media_route_provider_) {
DVLOG_WITH_INSTANCE(1) << "Extension is awake, awaiting ProvideMediaRouter "
" to be called.";
EnqueueTask(request);
} else {
request.Run();
}
}
void MediaRouterMojoImpl::AttemptWakeEventPage() {
++wakeup_attempt_count_;
if (wakeup_attempt_count_ > kMaxWakeupAttemptCount) {
DLOG_WITH_INSTANCE(ERROR) << "Attempted too many times to wake up event "
<< "page.";
DrainPendingRequests();
wakeup_attempt_count_ = 0;
MediaRouterMojoMetrics::RecordMediaRouteProviderWakeup(
MediaRouteProviderWakeup::ERROR_TOO_MANY_RETRIES);
return;
}
DVLOG_WITH_INSTANCE(1) << "Attempting to wake up event page: attempt "
<< wakeup_attempt_count_;
// This return false if the extension is already awake.
// Callback is bound using WeakPtr because |event_page_tracker_| outlives
// |this|.
if (!event_page_tracker_->WakeEventPage(
media_route_provider_extension_id_,
base::Bind(&MediaRouterMojoImpl::EventPageWakeComplete,
weak_factory_.GetWeakPtr()))) {
DLOG_WITH_INSTANCE(ERROR) << "Failed to schedule a wakeup for event page.";
}
}
void MediaRouterMojoImpl::ExecutePendingRequests() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(media_route_provider_);
DCHECK(event_page_tracker_);
DCHECK(!media_route_provider_extension_id_.empty());
for (const auto& next_request : pending_requests_)
next_request.Run();
pending_requests_.clear();
}
void MediaRouterMojoImpl::EventPageWakeComplete(bool success) {
if (success) {
MediaRouterMojoMetrics::RecordMediaRouteProviderWakeReason(
current_wake_reason_);
ClearWakeReason();
MediaRouterMojoMetrics::RecordMediaRouteProviderWakeup(
MediaRouteProviderWakeup::SUCCESS);
return;
}
// This is likely an non-retriable error. Drop the pending requests.
DLOG_WITH_INSTANCE(ERROR)
<< "An error encountered while waking the event page.";
ClearWakeReason();
DrainPendingRequests();
MediaRouterMojoMetrics::RecordMediaRouteProviderWakeup(
MediaRouteProviderWakeup::ERROR_UNKNOWN);
}
void MediaRouterMojoImpl::DrainPendingRequests() {
DLOG_WITH_INSTANCE(ERROR)
<< "Draining request queue. (queue-length=" << pending_requests_.size()
<< ")";
pending_requests_.clear();
}
void MediaRouterMojoImpl::SetWakeReason(MediaRouteProviderWakeReason reason) {
DCHECK(reason != MediaRouteProviderWakeReason::TOTAL_COUNT);
if (current_wake_reason_ == MediaRouteProviderWakeReason::TOTAL_COUNT)
current_wake_reason_ = reason;
}
void MediaRouterMojoImpl::ClearWakeReason() {
DCHECK(current_wake_reason_ != MediaRouteProviderWakeReason::TOTAL_COUNT);
current_wake_reason_ = MediaRouteProviderWakeReason::TOTAL_COUNT;
}
#if defined(OS_WIN)
void MediaRouterMojoImpl::EnsureMdnsDiscoveryEnabled() {
if (is_mdns_enabled_)
return;
SetWakeReason(MediaRouteProviderWakeReason::ENABLE_MDNS_DISCOVERY);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoEnsureMdnsDiscoveryEnabled,
base::Unretained(this)));
should_enable_mdns_discovery_ = true;
}
void MediaRouterMojoImpl::DoEnsureMdnsDiscoveryEnabled() {
DVLOG_WITH_INSTANCE(1) << "DoEnsureMdnsDiscoveryEnabled";
if (!is_mdns_enabled_) {
media_route_provider_->EnableMdnsDiscovery();
is_mdns_enabled_ = true;
}
}
void MediaRouterMojoImpl::OnFirewallCheckComplete(
bool firewall_can_use_local_ports) {
if (firewall_can_use_local_ports)
EnsureMdnsDiscoveryEnabled();
}
#endif
void MediaRouterMojoImpl::UpdateMediaSinks(
const MediaSource::Id& source_id) {
SetWakeReason(MediaRouteProviderWakeReason::UPDATE_MEDIA_SINKS);
RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoUpdateMediaSinks,
base::Unretained(this), source_id));
}
void MediaRouterMojoImpl::DoUpdateMediaSinks(
const MediaSource::Id& source_id) {
DVLOG_WITH_INSTANCE(1) << "DoUpdateMediaSinks" << source_id;
media_route_provider_->UpdateMediaSinks(source_id);
}
} // namespace media_router