| // Copyright 2019 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/providers/cast/cast_activity_record.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/optional.h" |
| #include "chrome/browser/media/router/providers/cast/cast_activity_manager.h" |
| #include "chrome/browser/media/router/providers/cast/cast_session_client.h" |
| #include "url/origin.h" |
| |
| using blink::mojom::PresentationConnectionCloseReason; |
| using blink::mojom::PresentationConnectionMessagePtr; |
| |
| namespace media_router { |
| |
| CastActivityRecord::CastActivityRecord(const MediaRoute& route, |
| const std::string& app_id) |
| : ActivityRecord(route, app_id) {} |
| |
| CastActivityRecord::~CastActivityRecord() = default; |
| |
| CastActivityRecordImpl::~CastActivityRecordImpl() = default; |
| |
| mojom::RoutePresentationConnectionPtr CastActivityRecordImpl::AddClient( |
| const CastMediaSource& source, |
| const url::Origin& origin, |
| int tab_id) { |
| const std::string& client_id = source.client_id(); |
| DCHECK(!base::ContainsKey(connected_clients_, client_id)); |
| std::unique_ptr<CastSessionClient> client = |
| client_factory_for_test_ |
| ? client_factory_for_test_->MakeClientForTest(client_id, origin, |
| tab_id) |
| : std::make_unique<CastSessionClientImpl>(client_id, origin, tab_id, |
| source.auto_join_policy(), |
| data_decoder_, this); |
| auto presentation_connection = client->Init(); |
| connected_clients_.emplace(client_id, std::move(client)); |
| |
| // Route is now local due to connected client. |
| route_.set_local(true); |
| return presentation_connection; |
| } |
| |
| void CastActivityRecordImpl::RemoveClient(const std::string& client_id) { |
| // Don't erase by key here as the |client_id| may be referring to the |
| // client being deleted. |
| auto it = connected_clients_.find(client_id); |
| if (it != connected_clients_.end()) |
| connected_clients_.erase(it); |
| } |
| |
| void CastActivityRecordImpl::SetOrUpdateSession(const CastSession& session, |
| const MediaSinkInternal& sink, |
| const std::string& hash_token) { |
| DVLOG(2) << "CastActivityRecordImpl::SetOrUpdateSession old session_id = " |
| << session_id_.value_or("<missing>") |
| << ", new session_id = " << session.session_id(); |
| if (!session_id_) { |
| session_id_ = session.session_id(); |
| } else { |
| DCHECK_EQ(*session_id_, session.session_id()); |
| for (auto& client : connected_clients_) |
| client.second->SendMessageToClient( |
| CreateUpdateSessionMessage(session, client.first, sink, hash_token)); |
| } |
| route_.set_description(session.GetRouteDescription()); |
| } |
| |
| cast_channel::Result CastActivityRecordImpl::SendAppMessageToReceiver( |
| const CastInternalMessage& cast_message) { |
| CastSessionClient* client = GetClient(cast_message.client_id()); |
| const CastSession* session = GetSession(); |
| if (!session) { |
| if (client && cast_message.sequence_number()) { |
| client->SendErrorCodeToClient( |
| *cast_message.sequence_number(), |
| CastInternalMessage::ErrorCode::kSessionError, |
| "Invalid session ID: " + session_id_.value_or("<missing>")); |
| } |
| |
| return cast_channel::Result::kFailed; |
| } |
| const std::string& message_namespace = cast_message.app_message_namespace(); |
| if (!base::ContainsKey(session->message_namespaces(), message_namespace)) { |
| DLOG(ERROR) << "Disallowed message namespace: " << message_namespace; |
| if (client && cast_message.sequence_number()) { |
| client->SendErrorCodeToClient( |
| *cast_message.sequence_number(), |
| CastInternalMessage::ErrorCode::kInvalidParameter, |
| "Invalid namespace: " + message_namespace); |
| } |
| return cast_channel::Result::kFailed; |
| } |
| return message_handler_->SendAppMessage( |
| GetCastChannelId(), |
| cast_channel::CreateCastMessage( |
| message_namespace, cast_message.app_message_body(), |
| cast_message.client_id(), session->transport_id())); |
| } |
| |
| base::Optional<int> CastActivityRecordImpl::SendMediaRequestToReceiver( |
| const CastInternalMessage& cast_message) { |
| CastSession* session = GetSession(); |
| if (!session) |
| return base::nullopt; |
| return message_handler_->SendMediaRequest( |
| GetCastChannelId(), cast_message.v2_message_body(), |
| cast_message.client_id(), session->transport_id()); |
| } |
| |
| void CastActivityRecordImpl::SendSetVolumeRequestToReceiver( |
| const CastInternalMessage& cast_message, |
| cast_channel::ResultCallback callback) { |
| message_handler_->SendSetVolumeRequest( |
| GetCastChannelId(), cast_message.v2_message_body(), |
| cast_message.client_id(), std::move(callback)); |
| } |
| |
| // TODO(jrw): Revise the name of this method. |
| void CastActivityRecordImpl::SendStopSessionMessageToReceiver( |
| const base::Optional<std::string>& client_id, |
| const std::string& hash_token, |
| mojom::MediaRouteProvider::TerminateRouteCallback callback) { |
| const std::string& sink_id = route_.media_sink_id(); |
| const MediaSinkInternal* sink = media_sink_service_->GetSinkById(sink_id); |
| DCHECK(sink); |
| DCHECK(session_id_); |
| |
| // TODO(jrw): Add test for this loop. |
| for (const auto& client : connected_clients()) { |
| client.second->SendMessageToClient( |
| CreateReceiverActionStopMessage(client.first, *sink, hash_token)); |
| } |
| |
| message_handler_->StopSession( |
| sink->cast_data().cast_channel_id, *session_id_, client_id, |
| activity_manager_->MakeResultCallbackForRoute(route_.media_route_id(), |
| std::move(callback))); |
| } |
| |
| void CastActivityRecordImpl::HandleLeaveSession(const std::string& client_id) { |
| auto client_it = connected_clients_.find(client_id); |
| CHECK(client_it != connected_clients_.end()); |
| auto& client = *client_it->second; |
| std::vector<std::string> leaving_client_ids; |
| for (const auto& pair : connected_clients_) { |
| if (pair.second->MatchesAutoJoinPolicy(client.origin(), client.tab_id())) |
| leaving_client_ids.push_back(pair.first); |
| } |
| |
| for (const auto& client_id : leaving_client_ids) { |
| auto leaving_client_it = connected_clients_.find(client_id); |
| CHECK(leaving_client_it != connected_clients_.end()); |
| leaving_client_it->second->CloseConnection( |
| PresentationConnectionCloseReason::CLOSED); |
| connected_clients_.erase(leaving_client_it); |
| } |
| } |
| |
| void CastActivityRecordImpl::SendMessageToClient( |
| const std::string& client_id, |
| PresentationConnectionMessagePtr message) { |
| auto it = connected_clients_.find(client_id); |
| if (it == connected_clients_.end()) { |
| DLOG(ERROR) << "Attempting to send message to nonexistent client: " |
| << client_id; |
| return; |
| } |
| it->second->SendMessageToClient(std::move(message)); |
| } |
| |
| void CastActivityRecordImpl::SendMediaStatusToClients( |
| const base::Value& media_status, |
| base::Optional<int> request_id) { |
| for (auto& client : connected_clients()) |
| client.second->SendMediaStatusToClient(media_status, request_id); |
| } |
| |
| void CastActivityRecordImpl::ClosePresentationConnections( |
| PresentationConnectionCloseReason close_reason) { |
| for (auto& client : connected_clients_) |
| client.second->CloseConnection(close_reason); |
| } |
| |
| void CastActivityRecordImpl::TerminatePresentationConnections() { |
| for (auto& client : connected_clients_) |
| client.second->TerminateConnection(); |
| } |
| |
| CastActivityRecordImpl::CastActivityRecordImpl( |
| const MediaRoute& route, |
| const std::string& app_id, |
| MediaSinkServiceBase* media_sink_service, |
| cast_channel::CastMessageHandler* message_handler, |
| CastSessionTracker* session_tracker, |
| DataDecoder* data_decoder, |
| CastActivityManagerBase* owner) |
| : CastActivityRecord(route, app_id), |
| media_sink_service_(media_sink_service), |
| message_handler_(message_handler), |
| session_tracker_(session_tracker), |
| data_decoder_(data_decoder), |
| activity_manager_(owner) {} |
| |
| CastSession* CastActivityRecordImpl::GetSession() { |
| DCHECK(session_id_); |
| CastSession* session = session_tracker_->GetSessionById(*session_id_); |
| if (!session) { |
| // TODO(crbug.com/905002): Add UMA metrics for this and other error |
| // conditions. |
| LOG(ERROR) << "Session not found: " << session_id_.value_or("<missing>"); |
| } |
| return session; |
| } |
| |
| int CastActivityRecordImpl::GetCastChannelId() { |
| const MediaSinkInternal* sink = media_sink_service_->GetSinkByRoute(route_); |
| if (!sink) { |
| // TODO(crbug.com/905002): Add UMA metrics for this and other error |
| // conditions. |
| LOG(ERROR) << "Sink not found for route: " << route_; |
| return -1; |
| } |
| return sink->cast_data().cast_channel_id; |
| } |
| |
| CastSessionClientFactoryForTest* |
| CastActivityRecordImpl::client_factory_for_test_ = nullptr; |
| |
| } // namespace media_router |