| // 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/media/router/providers/cast/mirroring_activity.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/values_test_util.h" |
| #include "chrome/browser/media/router/providers/cast/cast_activity_test_base.h" |
| #include "chrome/browser/media/router/providers/cast/mock_mirroring_service_host.h" |
| #include "chrome/browser/media/router/providers/cast/test_util.h" |
| #include "chrome/browser/media/router/test/media_router_mojo_test.h" |
| #include "chrome/browser/media/router/test/mock_mojo_media_router.h" |
| #include "components/media_router/common/mojom/debugger.mojom.h" |
| #include "components/media_router/common/providers/cast/channel/cast_test_util.h" |
| #include "components/mirroring/mojom/session_parameters.mojom.h" |
| #include "media/base/media_switches.h" |
| #include "media/cast/constants.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/mojom/presentation/presentation.mojom.h" |
| |
| using base::test::IsJson; |
| using testing::_; |
| using testing::NiceMock; |
| using testing::WithArg; |
| |
| namespace media_router { |
| namespace { |
| |
| constexpr int kFrameTreeNodeId = 123; |
| constexpr int kTabId = 234; |
| constexpr char kDescription[] = ""; |
| constexpr char kDesktopMediaId[] = "theDesktopMediaId"; |
| constexpr char kPresentationId[] = "thePresentationId"; |
| constexpr char kDestinationId[] = "theTransportId"; |
| |
| // Metrics constants. |
| constexpr char kHistogramSessionLength[] = |
| "MediaRouter.CastStreaming.Session.Length"; |
| constexpr char kHistogramSessionLengthAccessCode[] = |
| "MediaRouter.CastStreaming.Session.Length.AccessCode"; |
| constexpr char kHistogramSessionLengthDesktop[] = |
| "MediaRouter.CastStreaming.Session.Length.Screen"; |
| constexpr char kHistogramSessionLengthOffscreenTab[] = |
| "MediaRouter.CastStreaming.Session.Length.OffscreenTab"; |
| constexpr char kHistogramSessionLengthTab[] = |
| "MediaRouter.CastStreaming.Session.Length.Tab"; |
| |
| class MockMirroringServiceHostFactory |
| : public mirroring::MirroringServiceHostFactory { |
| public: |
| MOCK_METHOD(std::unique_ptr<mirroring::MirroringServiceHost>, |
| GetForTab, |
| (int32_t frame_tree_node_id)); |
| MOCK_METHOD(std::unique_ptr<mirroring::MirroringServiceHost>, |
| GetForDesktop, |
| (const absl::optional<std::string>& media_id)); |
| MOCK_METHOD(std::unique_ptr<mirroring::MirroringServiceHost>, |
| GetForOffscreenTab, |
| (const GURL& presentation_url, |
| const std::string& presentation_id, |
| int32_t frame_tree_node_id)); |
| }; |
| |
| class MockCastMessageChannel : public mirroring::mojom::CastMessageChannel { |
| public: |
| MOCK_METHOD(void, OnMessage, (mirroring::mojom::CastMessagePtr message)); |
| }; |
| |
| class MockMediaRouterDebugger : public mojom::Debugger { |
| public: |
| MOCK_METHOD(void, |
| ShouldFetchMirroringStats, |
| (base::OnceCallback<void(bool)> callback), |
| (override)); |
| MOCK_METHOD(void, |
| OnMirroringStats, |
| (const base::Value json_stats_cb), |
| (override)); |
| MOCK_METHOD(void, |
| BindReceiver, |
| (mojo::PendingReceiver<mojom::Debugger> receiver), |
| (override)); |
| }; |
| |
| } // namespace |
| |
| class MirroringActivityTest |
| : public CastActivityTestBase, |
| public testing::WithParamInterface<const char* /*namespace*/> { |
| protected: |
| void SetUp() override { |
| CastActivityTestBase::SetUp(); |
| |
| auto make_mirroring_service = |
| [this]() -> std::unique_ptr<MockMirroringServiceHost> { |
| if (!mirroring_service_) { |
| auto mirroring_service = std::make_unique<MockMirroringServiceHost>(); |
| mirroring_service_ = mirroring_service.get(); |
| return mirroring_service; |
| } |
| return nullptr; |
| }; |
| |
| ON_CALL(mirroring_service_host_factory_, GetForTab) |
| .WillByDefault(make_mirroring_service); |
| ON_CALL(mirroring_service_host_factory_, GetForDesktop) |
| .WillByDefault(make_mirroring_service); |
| ON_CALL(mirroring_service_host_factory_, GetForOffscreenTab) |
| .WillByDefault(make_mirroring_service); |
| |
| ON_CALL(media_router_, GetDebugger(_)) |
| .WillByDefault([this](mojo::PendingReceiver<mojom::Debugger> receiver) { |
| auto debugger = std::make_unique<MockMediaRouterDebugger>(); |
| debugger_object_ = debugger.get(); |
| mojo::MakeSelfOwnedReceiver(std::move(debugger), std::move(receiver)); |
| }); |
| } |
| |
| void MakeActivity() { MakeActivity(MediaSource::ForTab(kTabId)); } |
| |
| void MakeActivity(const MediaSource& source, |
| int frame_tree_node_id = kFrameTreeNodeId, |
| CastDiscoveryType discovery_type = CastDiscoveryType::kMdns, |
| bool enable_rtcp_reporting = false) { |
| CastSinkExtraData cast_data; |
| cast_data.cast_channel_id = kChannelId; |
| cast_data.capabilities = cast_channel::AUDIO_OUT | cast_channel::VIDEO_OUT; |
| cast_data.discovery_type = discovery_type; |
| MediaRoute route(kRouteId, source, kSinkId, kDescription, route_is_local_); |
| route.set_presentation_id(kPresentationId); |
| activity_ = std::make_unique<MirroringActivity>( |
| route, kAppId, &message_handler_, &session_tracker_, frame_tree_node_id, |
| cast_data, on_stop_.Get(), on_source_changed_.Get()); |
| |
| activity_->CreateMojoBindings(&media_router_); |
| |
| // This needs to be called before the mojo bindings are created, since we |
| // are creating the debugger_object_ at that point. |
| ON_CALL(*debugger_object_, ShouldFetchMirroringStats) |
| .WillByDefault( |
| [enable_rtcp_reporting](base::OnceCallback<void(bool)> callback) { |
| std::move(callback).Run(enable_rtcp_reporting); |
| }); |
| activity_->CreateMirroringServiceHost(&mirroring_service_host_factory_); |
| RunUntilIdle(); |
| |
| if (route_is_local_) { |
| EXPECT_CALL(*mirroring_service_, Start) |
| .WillOnce(WithArg<3>( |
| [this](mojo::PendingReceiver<mirroring::mojom::CastMessageChannel> |
| inbound_channel) { |
| ASSERT_FALSE(channel_to_service_); |
| auto channel = std::make_unique<MockCastMessageChannel>(); |
| channel_to_service_ = channel.get(); |
| mojo::MakeSelfOwnedReceiver(std::move(channel), |
| std::move(inbound_channel)); |
| })); |
| } |
| |
| activity_->SetOrUpdateSession(*session_, sink_, kHashToken); |
| RunUntilIdle(); |
| } |
| |
| const std::string& MessageSourceId() const { |
| return message_handler_.source_id(); |
| } |
| |
| bool route_is_local_ = true; |
| raw_ptr<MockCastMessageChannel> channel_to_service_ = nullptr; |
| raw_ptr<MockMediaRouterDebugger> debugger_object_ = nullptr; |
| raw_ptr<MockMirroringServiceHost> mirroring_service_ = nullptr; |
| NiceMock<MockMirroringServiceHostFactory> mirroring_service_host_factory_; |
| NiceMock<MockMojoMediaRouter> media_router_; |
| base::MockCallback<MirroringActivity::OnStopCallback> on_stop_; |
| base::MockCallback<OnSourceChangedCallback> on_source_changed_; |
| std::unique_ptr<MirroringActivity> activity_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(Namespaces, |
| MirroringActivityTest, |
| testing::Values(mirroring::mojom::kWebRtcNamespace, |
| mirroring::mojom::kRemotingNamespace)); |
| |
| TEST_F(MirroringActivityTest, MirrorDesktop) { |
| base::HistogramTester uma_recorder; |
| EXPECT_CALL(mirroring_service_host_factory_, |
| GetForDesktop(absl::optional<std::string>(kDesktopMediaId))); |
| MediaSource source = MediaSource::ForDesktop(kDesktopMediaId, true); |
| ASSERT_TRUE(source.IsDesktopMirroringSource()); |
| MakeActivity(source); |
| |
| activity_->DidStart(); |
| activity_.reset(); |
| |
| uma_recorder.ExpectTotalCount(kHistogramSessionLength, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthDesktop, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthTab, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthOffscreenTab, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthAccessCode, 0); |
| } |
| |
| TEST_F(MirroringActivityTest, MirrorTab) { |
| base::HistogramTester uma_recorder; |
| EXPECT_CALL(mirroring_service_host_factory_, GetForTab(kFrameTreeNodeId)); |
| MediaSource source = MediaSource::ForTab(kTabId); |
| ASSERT_TRUE(source.IsTabMirroringSource()); |
| MakeActivity(source); |
| |
| activity_->DidStart(); |
| activity_.reset(); |
| |
| uma_recorder.ExpectTotalCount(kHistogramSessionLength, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthDesktop, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthTab, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthOffscreenTab, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthAccessCode, 0); |
| } |
| |
| TEST_F(MirroringActivityTest, CreateMojoBindingsForTabWithCastAppUrl) { |
| base::HistogramTester uma_recorder; |
| EXPECT_CALL(mirroring_service_host_factory_, GetForTab(kFrameTreeNodeId)); |
| auto site_initiated_mirroring_source = |
| CastMediaSource::ForSiteInitiatedMirroring(); |
| MediaSource source(site_initiated_mirroring_source->source_id()); |
| ASSERT_TRUE(source.IsCastPresentationUrl()); |
| MakeActivity(source); |
| |
| activity_->DidStart(); |
| activity_.reset(); |
| |
| uma_recorder.ExpectTotalCount(kHistogramSessionLength, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthDesktop, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthTab, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthOffscreenTab, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthAccessCode, 0); |
| } |
| |
| TEST_F(MirroringActivityTest, MirrorOffscreenTab) { |
| base::HistogramTester uma_recorder; |
| static constexpr char kUrl[] = "http://wikipedia.org"; |
| GURL url(kUrl); |
| EXPECT_CALL(mirroring_service_host_factory_, |
| GetForOffscreenTab(url, kPresentationId, kFrameTreeNodeId)); |
| MediaSource source = MediaSource::ForPresentationUrl(url); |
| ASSERT_FALSE(source.IsCastPresentationUrl()); |
| MakeActivity(source); |
| |
| activity_->DidStart(); |
| activity_.reset(); |
| |
| uma_recorder.ExpectTotalCount(kHistogramSessionLength, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthDesktop, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthTab, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthOffscreenTab, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthAccessCode, 0); |
| } |
| |
| TEST_F(MirroringActivityTest, MirrorAccessCode) { |
| base::HistogramTester uma_recorder; |
| EXPECT_CALL(mirroring_service_host_factory_, GetForTab(kFrameTreeNodeId)); |
| MediaSource source = MediaSource::ForTab(kTabId); |
| ASSERT_TRUE(source.IsTabMirroringSource()); |
| MakeActivity(source, kFrameTreeNodeId, |
| CastDiscoveryType::kAccessCodeManualEntry); |
| |
| activity_->DidStart(); |
| activity_.reset(); |
| |
| uma_recorder.ExpectTotalCount(kHistogramSessionLength, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthDesktop, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthTab, 1); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthOffscreenTab, 0); |
| uma_recorder.ExpectTotalCount(kHistogramSessionLengthAccessCode, 1); |
| } |
| |
| TEST_F(MirroringActivityTest, OnError) { |
| MakeActivity(); |
| EXPECT_CALL(on_stop_, Run()); |
| activity_->OnError(mirroring::mojom::SessionError::CAST_TRANSPORT_ERROR); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(MirroringActivityTest, DidStop) { |
| MakeActivity(); |
| EXPECT_CALL(on_stop_, Run()); |
| activity_->DidStop(); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(MirroringActivityTest, SendWebRtc) { |
| MakeActivity(); |
| static constexpr char kPayload[] = R"({"foo": "bar"})"; |
| EXPECT_CALL(message_handler_, SendCastMessage(kChannelId, _)) |
| .WillOnce( |
| WithArg<1>([this](const cast::channel::CastMessage& cast_message) { |
| EXPECT_EQ(message_handler_.source_id(), cast_message.source_id()); |
| EXPECT_EQ(kDestinationId, cast_message.destination_id()); |
| EXPECT_EQ(mirroring::mojom::kWebRtcNamespace, |
| cast_message.namespace_()); |
| EXPECT_TRUE(cast_message.has_payload_utf8()); |
| EXPECT_THAT(cast_message.payload_utf8(), IsJson(kPayload)); |
| EXPECT_FALSE(cast_message.has_payload_binary()); |
| return cast_channel::Result::kOk; |
| })); |
| |
| activity_->OnMessage( |
| mirroring::mojom::CastMessage::New("the_namespace", kPayload)); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(MirroringActivityTest, SendRemoting) { |
| MakeActivity(); |
| static constexpr char kPayload[] = R"({"type": "RPC"})"; |
| EXPECT_CALL(message_handler_, SendCastMessage(kChannelId, _)) |
| .WillOnce(WithArg<1>([](const cast::channel::CastMessage& cast_message) { |
| EXPECT_EQ(mirroring::mojom::kRemotingNamespace, |
| cast_message.namespace_()); |
| return cast_channel::Result::kOk; |
| })); |
| |
| activity_->OnMessage( |
| mirroring::mojom::CastMessage::New("the_namespace", kPayload)); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(MirroringActivityTest, OnAppMessageWrongNamespace) { |
| MakeActivity(); |
| EXPECT_CALL(*channel_to_service_, OnMessage).Times(0); |
| cast::channel::CastMessage message; |
| message.set_namespace_("wrong_namespace"); |
| message.set_destination_id(kDestinationId); |
| message.set_source_id(MessageSourceId()); |
| activity_->OnAppMessage(message); |
| } |
| |
| TEST_P(MirroringActivityTest, OnAppMessageWrongDestination) { |
| MakeActivity(); |
| EXPECT_CALL(*channel_to_service_, OnMessage).Times(0); |
| cast::channel::CastMessage message; |
| message.set_namespace_(GetParam()); |
| message.set_destination_id("someOtherDestination"); |
| message.set_source_id(MessageSourceId()); |
| activity_->OnAppMessage(message); |
| } |
| |
| TEST_P(MirroringActivityTest, OnAppMessageWrongSource) { |
| MakeActivity(); |
| EXPECT_CALL(*channel_to_service_, OnMessage).Times(0); |
| cast::channel::CastMessage message; |
| message.set_namespace_(GetParam()); |
| message.set_destination_id(kDestinationId); |
| message.set_source_id("someRandomStranger"); |
| activity_->OnAppMessage(message); |
| } |
| |
| TEST_P(MirroringActivityTest, OnAppMessageWrongNonlocal) { |
| route_is_local_ = false; |
| MakeActivity(); |
| ASSERT_FALSE(channel_to_service_); |
| cast::channel::CastMessage message; |
| message.set_namespace_(GetParam()); |
| message.set_destination_id(kDestinationId); |
| message.set_source_id(MessageSourceId()); |
| activity_->OnAppMessage(message); |
| } |
| |
| TEST_P(MirroringActivityTest, OnAppMessage) { |
| MakeActivity(); |
| |
| static constexpr char kPayload[] = R"({"foo": "bar"})"; |
| |
| EXPECT_CALL(*channel_to_service_, OnMessage) |
| .WillOnce([](mirroring::mojom::CastMessagePtr message) { |
| EXPECT_EQ(GetParam(), message->message_namespace); |
| EXPECT_EQ(kPayload, message->json_format_data); |
| }); |
| |
| cast::channel::CastMessage message; |
| message.set_namespace_(GetParam()); |
| message.set_destination_id(kDestinationId); |
| message.set_source_id(MessageSourceId()); |
| message.set_protocol_version( |
| cast::channel::CastMessage_ProtocolVersion_CASTV2_1_0); |
| message.set_payload_utf8(kPayload); |
| activity_->OnAppMessage(message); |
| } |
| |
| TEST_F(MirroringActivityTest, OnInternalMessageNonlocal) { |
| route_is_local_ = false; |
| MakeActivity(); |
| ASSERT_FALSE(channel_to_service_); |
| activity_->OnInternalMessage( |
| cast_channel::InternalMessage(cast_channel::CastMessageType::kPing, |
| "the_namespace", base::Value::Dict())); |
| } |
| |
| TEST_F(MirroringActivityTest, OnInternalMessage) { |
| MakeActivity(); |
| |
| static constexpr char kPayload[] = R"({"foo": "bar"})"; |
| static constexpr char kNamespace[] = "the_namespace"; |
| |
| EXPECT_CALL(*channel_to_service_, OnMessage) |
| .WillOnce([](mirroring::mojom::CastMessagePtr message) { |
| EXPECT_EQ(kNamespace, message->message_namespace); |
| EXPECT_THAT(message->json_format_data, IsJson(kPayload)); |
| }); |
| |
| activity_->OnInternalMessage(cast_channel::InternalMessage( |
| cast_channel::CastMessageType::kPing, kNamespace, |
| base::test::ParseJsonDict(kPayload))); |
| } |
| |
| TEST_F(MirroringActivityTest, GetScrubbedLogMessage) { |
| static constexpr char message[] = R"( |
| { |
| "offer": { |
| "supportedStreams": [ |
| { |
| "aesIvMask": "Mask_A", |
| "aesKey": "Key_A" |
| }, |
| { |
| "aesIvMask": "Mask_B", |
| "aesKey": "Key_B" |
| } |
| ] |
| }, |
| "type": "OFFER" |
| })"; |
| static constexpr char scrubbed_message[] = R"( |
| { |
| "offer": { |
| "supportedStreams": [ |
| { |
| "aesIvMask": "AES_IV_MASK", |
| "aesKey": "AES_KEY" |
| }, |
| { |
| "aesIvMask": "AES_IV_MASK", |
| "aesKey": "AES_KEY" |
| } |
| ] |
| }, |
| "type": "OFFER" |
| })"; |
| |
| absl::optional<base::Value> message_json = base::JSONReader::Read(message); |
| EXPECT_TRUE(message_json); |
| EXPECT_TRUE(message_json.value().is_dict()); |
| EXPECT_THAT(scrubbed_message, |
| base::test::IsJson(MirroringActivity::GetScrubbedLogMessage( |
| message_json.value().GetDict()))); |
| } |
| |
| // Site-initiated mirroring activities must be able to send messages to the |
| // client, which may be expecting to receive Cast protocol messages. |
| // See crbug.com/1078481 for context. |
| TEST_F(MirroringActivityTest, SendMessageToClient) { |
| MakeActivity(); |
| |
| static constexpr char kClientId[] = "theClientId"; |
| blink::mojom::PresentationConnectionMessagePtr message = |
| blink::mojom::PresentationConnectionMessage::NewMessage("\"theMessage\""); |
| auto* message_ptr = message.get(); |
| auto* client = AddMockClient(activity_.get(), kClientId, 1); |
| EXPECT_CALL(*client, SendMessageToClient).WillOnce([=](auto arg) { |
| EXPECT_EQ(message_ptr, arg.get()); |
| }); |
| activity_->SendMessageToClient(kClientId, std::move(message)); |
| } |
| |
| TEST_F(MirroringActivityTest, OnSourceChanged) { |
| MakeActivity(); |
| |
| // A random int indicating the new tab source. |
| const int new_tab_source = 3; |
| |
| EXPECT_CALL(on_source_changed_, Run(kFrameTreeNodeId, new_tab_source)); |
| |
| EXPECT_CALL(*mirroring_service_, GetTabSourceId()) |
| .WillOnce(testing::Return(new_tab_source)); |
| |
| EXPECT_EQ(activity_->frame_tree_node_id_, kFrameTreeNodeId); |
| activity_->OnSourceChanged(); |
| EXPECT_EQ(activity_->frame_tree_node_id_, new_tab_source); |
| RunUntilIdle(); |
| testing::Mock::VerifyAndClearExpectations(mirroring_service_); |
| |
| // Nothing should happen as no value was returned for tab source. |
| EXPECT_CALL(*mirroring_service_, GetTabSourceId()) |
| .WillOnce(testing::Return(absl::nullopt)); |
| activity_->OnSourceChanged(); |
| EXPECT_EQ(activity_->frame_tree_node_id_, new_tab_source); |
| testing::Mock::VerifyAndClearExpectations(mirroring_service_); |
| } |
| |
| TEST_F(MirroringActivityTest, OnSourceChangedNotifiesMediaStatusObserver) { |
| MakeActivity(); |
| mojo::PendingRemote<mojom::MediaStatusObserver> observer_pending_remote; |
| NiceMock<MockMediaStatusObserver> media_status_observer = |
| NiceMock<MockMediaStatusObserver>( |
| observer_pending_remote.InitWithNewPipeAndPassReceiver()); |
| mojo::Remote<mojom::MediaController> media_controller; |
| activity_->CreateMediaController( |
| media_controller.BindNewPipeAndPassReceiver(), |
| std::move(observer_pending_remote)); |
| RunUntilIdle(); |
| |
| // A random int indicating the new tab source. |
| const int new_tab_source = 3; |
| |
| EXPECT_CALL(on_source_changed_, Run(kFrameTreeNodeId, new_tab_source)); |
| |
| EXPECT_CALL(*mirroring_service_, GetTabSourceId()) |
| .WillOnce(testing::Return(new_tab_source)); |
| |
| EXPECT_CALL(media_status_observer, OnMediaStatusUpdated(_)) |
| .WillOnce([&](mojom::MediaStatusPtr status) { |
| EXPECT_EQ(mojom::MediaStatus::PlayState::PLAYING, status->play_state); |
| }); |
| |
| activity_->OnSourceChanged(); |
| base::RunLoop().RunUntilIdle(); |
| testing::Mock::VerifyAndClearExpectations(&media_status_observer); |
| } |
| |
| TEST_F(MirroringActivityTest, ReportsNotEnabledByDefault) { |
| MediaSource source = MediaSource::ForDesktop(kDesktopMediaId, true); |
| MakeActivity(source); |
| |
| activity_->DidStart(); |
| EXPECT_FALSE(activity_->should_fetch_stats_on_start_); |
| } |
| |
| TEST_F(MirroringActivityTest, EnableRtcpReports) { |
| MediaSource source = MediaSource::ForDesktop(kDesktopMediaId, true); |
| MakeActivity(source, kFrameTreeNodeId, CastDiscoveryType::kMdns, true); |
| |
| activity_->DidStart(); |
| EXPECT_TRUE(activity_->should_fetch_stats_on_start_); |
| |
| ON_CALL(*mirroring_service_, GetMirroringStats(_)) |
| .WillByDefault([](base::OnceCallback<void(const base::Value)> callback) { |
| std::move(callback).Run(base::Value("foo")); |
| }); |
| |
| EXPECT_CALL(*debugger_object_, OnMirroringStats) |
| .WillOnce(testing::Invoke([&](const base::Value json_stats_cb) { |
| EXPECT_EQ(base::Value("foo"), json_stats_cb); |
| })); |
| // A call to fetch mirroring stats should have been posted at this point. Fast |
| // forward past the delay of this posted task. |
| task_environment_.FastForwardBy(media::cast::kRtcpReportInterval); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(MirroringActivityTest, Pause) { |
| MakeActivity(); |
| mojo::PendingRemote<mojom::MediaStatusObserver> observer_pending_remote; |
| NiceMock<MockMediaStatusObserver> media_status_observer = |
| NiceMock<MockMediaStatusObserver>( |
| observer_pending_remote.InitWithNewPipeAndPassReceiver()); |
| mojo::Remote<mojom::MediaController> media_controller; |
| activity_->CreateMediaController( |
| media_controller.BindNewPipeAndPassReceiver(), |
| std::move(observer_pending_remote)); |
| RunUntilIdle(); |
| |
| mojom::MediaStatusPtr expected_status = mojom::MediaStatus::New(); |
| expected_status->play_state = mojom::MediaStatus::PlayState::PAUSED; |
| auto cb = [&](base::OnceClosure callback) { std::move(callback).Run(); }; |
| EXPECT_CALL(*mirroring_service_, Pause(_)).WillOnce(testing::Invoke(cb)); |
| EXPECT_CALL(media_status_observer, OnMediaStatusUpdated(_)) |
| .WillOnce([&](mojom::MediaStatusPtr status) { |
| EXPECT_EQ(expected_status->play_state, status->play_state); |
| }); |
| |
| activity_->Pause(); |
| base::RunLoop().RunUntilIdle(); |
| testing::Mock::VerifyAndClearExpectations(&media_status_observer); |
| } |
| |
| TEST_F(MirroringActivityTest, Play) { |
| MakeActivity(); |
| mojo::PendingRemote<mojom::MediaStatusObserver> observer_pending_remote; |
| NiceMock<MockMediaStatusObserver> media_status_observer = |
| NiceMock<MockMediaStatusObserver>( |
| observer_pending_remote.InitWithNewPipeAndPassReceiver()); |
| mojo::Remote<mojom::MediaController> media_controller; |
| activity_->CreateMediaController( |
| media_controller.BindNewPipeAndPassReceiver(), |
| std::move(observer_pending_remote)); |
| RunUntilIdle(); |
| |
| mojom::MediaStatusPtr expected_status = mojom::MediaStatus::New(); |
| expected_status->play_state = mojom::MediaStatus::PlayState::PLAYING; |
| auto cb = [&](base::OnceClosure callback) { std::move(callback).Run(); }; |
| EXPECT_CALL(*mirroring_service_, Resume(_)).WillOnce(testing::Invoke(cb)); |
| EXPECT_CALL(media_status_observer, OnMediaStatusUpdated(_)) |
| .WillOnce([&](mojom::MediaStatusPtr status) { |
| EXPECT_EQ(expected_status->play_state, status->play_state); |
| }); |
| |
| activity_->Play(); |
| base::RunLoop().RunUntilIdle(); |
| testing::Mock::VerifyAndClearExpectations(&media_status_observer); |
| } |
| |
| TEST_F(MirroringActivityTest, PauseAndPlay) { |
| base::HistogramTester uma_recorder; |
| EXPECT_CALL(mirroring_service_host_factory_, GetForTab(kFrameTreeNodeId)); |
| MediaSource source = MediaSource::ForTab(kTabId); |
| MakeActivity(source, kFrameTreeNodeId, |
| CastDiscoveryType::kAccessCodeManualEntry); |
| auto cb = [&](base::OnceClosure callback) { std::move(callback).Run(); }; |
| EXPECT_CALL(*mirroring_service_, Pause(_)).WillOnce(testing::Invoke(cb)); |
| EXPECT_CALL(*mirroring_service_, Resume(_)).WillOnce(testing::Invoke(cb)); |
| |
| activity_->DidStart(); |
| activity_->Pause(); |
| base::RunLoop().RunUntilIdle(); |
| activity_->Play(); |
| base::RunLoop().RunUntilIdle(); |
| activity_.reset(); |
| base::RunLoop().RunUntilIdle(); |
| |
| uma_recorder.ExpectTotalCount("AccessCodeCast.Session.FreezeCount", 1); |
| uma_recorder.ExpectTotalCount("AccessCodeCast.Session.FreezeDuration", 1); |
| } |
| |
| TEST_F(MirroringActivityTest, PauseAndReset) { |
| base::HistogramTester uma_recorder; |
| EXPECT_CALL(mirroring_service_host_factory_, GetForTab(kFrameTreeNodeId)); |
| MediaSource source = MediaSource::ForTab(kTabId); |
| MakeActivity(source, kFrameTreeNodeId, |
| CastDiscoveryType::kAccessCodeManualEntry); |
| auto cb = [&](base::OnceClosure callback) { std::move(callback).Run(); }; |
| EXPECT_CALL(*mirroring_service_, Pause(_)).WillOnce(testing::Invoke(cb)); |
| |
| activity_->DidStart(); |
| activity_->Pause(); |
| base::RunLoop().RunUntilIdle(); |
| activity_.reset(); |
| base::RunLoop().RunUntilIdle(); |
| |
| uma_recorder.ExpectTotalCount("AccessCodeCast.Session.FreezeCount", 1); |
| uma_recorder.ExpectTotalCount("AccessCodeCast.Session.FreezeDuration", 1); |
| } |
| |
| TEST_F(MirroringActivityTest, OnRemotingStateChanged) { |
| MakeActivity(); |
| mojo::PendingRemote<mojom::MediaStatusObserver> observer_pending_remote; |
| NiceMock<MockMediaStatusObserver> media_status_observer = |
| NiceMock<MockMediaStatusObserver>( |
| observer_pending_remote.InitWithNewPipeAndPassReceiver()); |
| mojo::Remote<mojom::MediaController> media_controller; |
| activity_->CreateMediaController( |
| media_controller.BindNewPipeAndPassReceiver(), |
| std::move(observer_pending_remote)); |
| RunUntilIdle(); |
| |
| mojom::MediaStatusPtr expected_status = mojom::MediaStatus::New(); |
| expected_status->play_state = mojom::MediaStatus::PlayState::PLAYING; |
| expected_status->can_play_pause = false; |
| EXPECT_CALL(media_status_observer, OnMediaStatusUpdated(_)) |
| .WillOnce([&](mojom::MediaStatusPtr status) { |
| EXPECT_EQ(expected_status->play_state, status->play_state); |
| EXPECT_EQ(expected_status->can_play_pause, status->can_play_pause); |
| }); |
| |
| activity_->OnRemotingStateChanged(/*is_remoting*/ true); |
| base::RunLoop().RunUntilIdle(); |
| testing::Mock::VerifyAndClearExpectations(&media_status_observer); |
| |
| expected_status->can_play_pause = true; |
| EXPECT_CALL(media_status_observer, OnMediaStatusUpdated(_)) |
| .WillOnce([&](mojom::MediaStatusPtr status) { |
| EXPECT_EQ(expected_status->play_state, status->play_state); |
| EXPECT_EQ(expected_status->can_play_pause, status->can_play_pause); |
| }); |
| |
| activity_->OnRemotingStateChanged(/*is_remoting*/ false); |
| base::RunLoop().RunUntilIdle(); |
| testing::Mock::VerifyAndClearExpectations(&media_status_observer); |
| } |
| |
| TEST_F(MirroringActivityTest, GetTargetPlayoutDelay) { |
| MakeActivity(); |
| |
| // Expect no command line switch will return the same value. |
| const base::TimeDelta default_playout_delay = base::Milliseconds(400); |
| EXPECT_EQ(activity_->GetTargetPlayoutDelay(default_playout_delay).value(), |
| default_playout_delay); |
| |
| // Expect no command line switch and a nullopt will return a nullopt. |
| EXPECT_EQ(activity_->GetTargetPlayoutDelay(absl::nullopt), absl::nullopt); |
| |
| // Test that an invalid switch (alpha-numeric string) will return the |
| // parameter value. |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII(switches::kCastMirroringTargetPlayoutDelay, |
| "foo"); |
| EXPECT_EQ(activity_->GetTargetPlayoutDelay(default_playout_delay).value(), |
| default_playout_delay); |
| |
| // Test that a valid switch will override the target playout delay. |
| const base::TimeDelta switch_playout_delay = base::Milliseconds(200); |
| command_line->AppendSwitchASCII( |
| switches::kCastMirroringTargetPlayoutDelay, |
| base::NumberToString(switch_playout_delay.InMilliseconds())); |
| EXPECT_EQ(activity_->GetTargetPlayoutDelay(switch_playout_delay).value(), |
| switch_playout_delay); |
| |
| // Test that returned value is the switch even with nullopt. |
| EXPECT_EQ(activity_->GetTargetPlayoutDelay(absl::nullopt).value(), |
| switch_playout_delay); |
| } |
| |
| } // namespace media_router |