| // 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/android/router/media_router_android.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind_helpers.h" |
| #include "base/guid.h" |
| #include "base/logging.h" |
| #include "base/stl_util.h" |
| #include "chrome/browser/android/tab_android.h" |
| #include "chrome/browser/media/router/media_routes_observer.h" |
| #include "chrome/browser/media/router/media_sinks_observer.h" |
| #include "chrome/browser/media/router/route_message_observer.h" |
| #include "chrome/browser/media/router/route_message_util.h" |
| #include "chrome/common/media_router/route_request_result.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/web_contents.h" |
| #include "url/gurl.h" |
| |
| namespace media_router { |
| |
| MediaRouterAndroid::PresentationConnectionProxy::PresentationConnectionProxy( |
| MediaRouterAndroid* media_router_android, |
| const MediaRoute::Id& route_id) |
| : binding_(this), |
| media_router_android_(media_router_android), |
| route_id_(route_id) {} |
| |
| MediaRouterAndroid::PresentationConnectionProxy:: |
| ~PresentationConnectionProxy() = default; |
| |
| mojom::RoutePresentationConnectionPtr |
| MediaRouterAndroid::PresentationConnectionProxy::Init() { |
| auto request = mojo::MakeRequest(&peer_); |
| peer_.set_connection_error_handler( |
| base::BindOnce(&MediaRouterAndroid::OnPresentationConnectionError, |
| base::Unretained(media_router_android_), route_id_)); |
| peer_->DidChangeState(blink::mojom::PresentationConnectionState::CONNECTED); |
| return mojom::RoutePresentationConnection::New(Bind(), std::move(request)); |
| } |
| |
| void MediaRouterAndroid::PresentationConnectionProxy::OnMessage( |
| blink::mojom::PresentationConnectionMessagePtr message) { |
| if (message->is_message()) |
| media_router_android_->SendRouteMessage(route_id_, message->get_message()); |
| } |
| |
| void MediaRouterAndroid::PresentationConnectionProxy::DidClose( |
| blink::mojom::PresentationConnectionCloseReason reason) { |
| auto& route_connections = |
| media_router_android_->presentation_connections_[route_id_]; |
| DCHECK(!route_connections.empty()); |
| base::EraseIf(route_connections, [this](const auto& connection) { |
| return connection.get() == this; |
| }); |
| } |
| |
| blink::mojom::PresentationConnectionPtrInfo |
| MediaRouterAndroid::PresentationConnectionProxy::Bind() { |
| blink::mojom::PresentationConnectionPtrInfo conn_info; |
| auto request = mojo::MakeRequest(&conn_info); |
| binding_.Bind(std::move(request)); |
| binding_.set_connection_error_handler( |
| base::BindOnce(&MediaRouterAndroid::OnPresentationConnectionError, |
| base::Unretained(media_router_android_), route_id_)); |
| return conn_info; |
| } |
| |
| void MediaRouterAndroid::PresentationConnectionProxy::SendMessage( |
| const std::string& message) { |
| DCHECK(peer_); |
| peer_->OnMessage( |
| blink::mojom::PresentationConnectionMessage::NewMessage(message)); |
| } |
| |
| MediaRouterAndroid::MediaRouteRequest::MediaRouteRequest( |
| const MediaSource& source, |
| const std::string& presentation_id, |
| MediaRouteResponseCallback callback) |
| : media_source(source), |
| presentation_id(presentation_id), |
| callback(std::move(callback)) {} |
| |
| MediaRouterAndroid::MediaRouteRequest::~MediaRouteRequest() {} |
| |
| MediaRouterAndroid::MediaRouterAndroid(content::BrowserContext*) |
| : bridge_(new MediaRouterAndroidBridge(this)) {} |
| |
| MediaRouterAndroid::~MediaRouterAndroid() {} |
| |
| const MediaRoute* MediaRouterAndroid::FindRouteBySource( |
| const MediaSource::Id& source_id) const { |
| for (const auto& route : active_routes_) { |
| if (route.media_source().id() == source_id) |
| return &route; |
| } |
| return nullptr; |
| } |
| |
| void MediaRouterAndroid::CreateRoute(const MediaSource::Id& source_id, |
| const MediaSink::Id& sink_id, |
| const url::Origin& origin, |
| content::WebContents* web_contents, |
| MediaRouteResponseCallback callback, |
| base::TimeDelta timeout, |
| bool incognito) { |
| DCHECK(callback); |
| // TODO(avayvod): Implement timeouts (crbug.com/583036). |
| std::string presentation_id = MediaRouterBase::CreatePresentationId(); |
| |
| int tab_id = -1; |
| TabAndroid* tab = |
| web_contents ? TabAndroid::FromWebContents(web_contents) : nullptr; |
| if (tab) |
| tab_id = tab->GetAndroidId(); |
| |
| bool is_incognito = |
| web_contents && web_contents->GetBrowserContext()->IsOffTheRecord(); |
| |
| int route_request_id = |
| route_requests_.Add(std::make_unique<MediaRouteRequest>( |
| MediaSource(source_id), presentation_id, std::move(callback))); |
| bridge_->CreateRoute(source_id, sink_id, presentation_id, origin, tab_id, |
| is_incognito, route_request_id); |
| } |
| |
| void MediaRouterAndroid::ConnectRouteByRouteId( |
| const MediaSource::Id& source, |
| const MediaRoute::Id& route_id, |
| const url::Origin& origin, |
| content::WebContents* web_contents, |
| MediaRouteResponseCallback callback, |
| base::TimeDelta timeout, |
| bool incognito) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void MediaRouterAndroid::JoinRoute(const MediaSource::Id& source_id, |
| const std::string& presentation_id, |
| const url::Origin& origin, |
| content::WebContents* web_contents, |
| MediaRouteResponseCallback callback, |
| base::TimeDelta timeout, |
| bool incognito) { |
| DCHECK(callback); |
| // TODO(avayvod): Implement timeouts (crbug.com/583036). |
| int tab_id = -1; |
| TabAndroid* tab = |
| web_contents ? TabAndroid::FromWebContents(web_contents) : nullptr; |
| if (tab) |
| tab_id = tab->GetAndroidId(); |
| |
| DVLOG(2) << "JoinRoute: " << source_id << ", " << presentation_id << ", " |
| << origin.GetURL().spec() << ", " << tab_id; |
| |
| int request_id = route_requests_.Add(std::make_unique<MediaRouteRequest>( |
| MediaSource(source_id), presentation_id, std::move(callback))); |
| bridge_->JoinRoute(source_id, presentation_id, origin, tab_id, request_id); |
| } |
| |
| void MediaRouterAndroid::TerminateRoute(const MediaRoute::Id& route_id) { |
| bridge_->TerminateRoute(route_id); |
| } |
| |
| void MediaRouterAndroid::SendRouteMessage(const MediaRoute::Id& route_id, |
| const std::string& message) { |
| bridge_->SendRouteMessage(route_id, message); |
| } |
| |
| void MediaRouterAndroid::SendRouteBinaryMessage( |
| const MediaRoute::Id& route_id, |
| std::unique_ptr<std::vector<uint8_t>> data) { |
| // Binary messaging is not supported on Android. |
| } |
| |
| void MediaRouterAndroid::OnUserGesture() {} |
| |
| void MediaRouterAndroid::SearchSinks( |
| const MediaSink::Id& sink_id, |
| const MediaSource::Id& source_id, |
| const std::string& search_input, |
| const std::string& domain, |
| MediaSinkSearchResponseCallback sink_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void MediaRouterAndroid::DetachRoute(const MediaRoute::Id& route_id) { |
| bridge_->DetachRoute(route_id); |
| RemoveRoute(route_id); |
| NotifyPresentationConnectionClose( |
| route_id, blink::mojom::PresentationConnectionCloseReason::CLOSED, |
| "Route closed normally"); |
| } |
| |
| bool MediaRouterAndroid::RegisterMediaSinksObserver( |
| MediaSinksObserver* observer) { |
| const std::string& source_id = observer->source().id(); |
| auto& observer_list = sinks_observers_[source_id]; |
| if (!observer_list) { |
| observer_list = std::make_unique<MediaSinksObserverList>(); |
| } else { |
| DCHECK(!observer_list->HasObserver(observer)); |
| } |
| |
| observer_list->AddObserver(observer); |
| return bridge_->StartObservingMediaSinks(source_id); |
| } |
| |
| void MediaRouterAndroid::UnregisterMediaSinksObserver( |
| MediaSinksObserver* observer) { |
| const std::string& source_id = observer->source().id(); |
| auto it = sinks_observers_.find(source_id); |
| if (it == sinks_observers_.end() || !it->second->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. |
| it->second->RemoveObserver(observer); |
| if (!it->second->might_have_observers()) { |
| sinks_observers_.erase(source_id); |
| bridge_->StopObservingMediaSinks(source_id); |
| } |
| } |
| |
| void MediaRouterAndroid::RegisterMediaRoutesObserver( |
| MediaRoutesObserver* observer) { |
| DVLOG(2) << "Added MediaRoutesObserver: " << observer; |
| if (!observer->source_id().empty()) |
| NOTIMPLEMENTED() << "Joinable routes query not implemented."; |
| |
| routes_observers_.AddObserver(observer); |
| } |
| |
| void MediaRouterAndroid::UnregisterMediaRoutesObserver( |
| MediaRoutesObserver* observer) { |
| if (!routes_observers_.HasObserver(observer)) |
| return; |
| routes_observers_.RemoveObserver(observer); |
| } |
| |
| void MediaRouterAndroid::RegisterRouteMessageObserver( |
| RouteMessageObserver* observer) { |
| NOTREACHED(); |
| } |
| |
| void MediaRouterAndroid::UnregisterRouteMessageObserver( |
| RouteMessageObserver* observer) { |
| NOTREACHED(); |
| } |
| |
| void MediaRouterAndroid::OnSinksReceived(const std::string& source_urn, |
| const std::vector<MediaSink>& sinks) { |
| auto it = sinks_observers_.find(source_urn); |
| if (it != sinks_observers_.end()) { |
| // TODO(imcheng): Pass origins to OnSinksUpdated (crbug.com/594858). |
| for (auto& observer : *it->second) |
| observer.OnSinksUpdated(sinks, std::vector<url::Origin>()); |
| } |
| } |
| |
| void MediaRouterAndroid::OnRouteCreated(const MediaRoute::Id& route_id, |
| const MediaSink::Id& sink_id, |
| int route_request_id, |
| bool is_local) { |
| MediaRouteRequest* request = route_requests_.Lookup(route_request_id); |
| if (!request) |
| return; |
| |
| MediaRoute route(route_id, request->media_source, sink_id, std::string(), |
| is_local, true); // TODO(avayvod): Populate for_display. |
| |
| std::unique_ptr<RouteRequestResult> result = |
| RouteRequestResult::FromSuccess(route, request->presentation_id); |
| auto& presentation_connections = presentation_connections_[route_id]; |
| presentation_connections.push_back( |
| std::make_unique<PresentationConnectionProxy>(this, route_id)); |
| auto& presentation_connection = *presentation_connections.back(); |
| std::move(request->callback).Run(presentation_connection.Init(), *result); |
| |
| route_requests_.Remove(route_request_id); |
| |
| active_routes_.push_back(route); |
| for (auto& observer : routes_observers_) |
| observer.OnRoutesUpdated(active_routes_, std::vector<MediaRoute::Id>()); |
| } |
| |
| void MediaRouterAndroid::OnRouteRequestError(const std::string& error_text, |
| int route_request_id) { |
| MediaRouteRequest* request = route_requests_.Lookup(route_request_id); |
| if (!request) |
| return; |
| |
| // TODO(imcheng): Provide a more specific result code. |
| std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError( |
| error_text, RouteRequestResult::UNKNOWN_ERROR); |
| std::move(request->callback).Run(nullptr, *result); |
| |
| route_requests_.Remove(route_request_id); |
| } |
| |
| void MediaRouterAndroid::OnRouteTerminated(const MediaRoute::Id& route_id) { |
| RemoveRoute(route_id); |
| NotifyPresentationConnectionStateChange( |
| route_id, blink::mojom::PresentationConnectionState::TERMINATED); |
| } |
| |
| void MediaRouterAndroid::OnRouteClosed( |
| const MediaRoute::Id& route_id, |
| const base::Optional<std::string>& error) { |
| RemoveRoute(route_id); |
| // TODO(crbug.com/882690): When the sending context is destroyed, tell MRP to |
| // clean up the connection. |
| if (error.has_value()) { |
| NotifyPresentationConnectionClose( |
| route_id, |
| blink::mojom::PresentationConnectionCloseReason::CONNECTION_ERROR, |
| error.value()); |
| } else { |
| NotifyPresentationConnectionClose( |
| route_id, blink::mojom::PresentationConnectionCloseReason::CLOSED, |
| "Remove route"); |
| } |
| } |
| |
| void MediaRouterAndroid::OnMessage(const MediaRoute::Id& route_id, |
| const std::string& message) { |
| auto entry = presentation_connections_.find(route_id); |
| if (entry != presentation_connections_.end()) { |
| // Note: Route-ID-to-presentation-ID mapping is done by route providers. |
| // Although the messages API (being deprecated) is based on route IDs, |
| // providers may use the same route for each presentation connection. This |
| // would result in broadcasting provider messages to all presentation |
| // connections. So although this loop may seem strange in the context of |
| // the Presentation API, it can't be avoided at the moment. |
| DCHECK_EQ(1u, entry->second.size()); |
| for (auto& connection : entry->second) { |
| connection->SendMessage(message); |
| } |
| } |
| } |
| |
| void MediaRouterAndroid::RemoveRoute(const MediaRoute::Id& route_id) { |
| presentation_connections_.erase(route_id); |
| for (auto it = active_routes_.begin(); it != active_routes_.end(); ++it) |
| if (it->media_route_id() == route_id) { |
| active_routes_.erase(it); |
| break; |
| } |
| |
| for (auto& observer : routes_observers_) |
| observer.OnRoutesUpdated(active_routes_, std::vector<MediaRoute::Id>()); |
| } |
| |
| std::unique_ptr<media::FlingingController> |
| MediaRouterAndroid::GetFlingingController(const MediaRoute::Id& route_id) { |
| return bridge_->GetFlingingController(route_id); |
| } |
| |
| void MediaRouterAndroid::OnPresentationConnectionError( |
| const std::string& route_id) { |
| presentation_connections_.erase(route_id); |
| } |
| |
| } // namespace media_router |