| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/media_router/browser/android/media_router_android.h" |
| |
| #include <memory> |
| |
| #include "base/android/jni_android.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/mock_callback.h" |
| #include "components/media_router/browser/android/media_router_android_bridge.h" |
| #include "components/media_router/browser/test/test_helper.h" |
| #include "content/public/browser/presentation_service_delegate.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/origin.h" |
| |
| // Must come after all headers that specialize FromJniType() / ToJniType(). |
| #include "components/media_router/browser/android/test_jni_headers/TestMediaRouterClient_jni.h" |
| |
| using blink::mojom::PresentationConnectionState; |
| using testing::_; |
| using testing::Expectation; |
| using testing::Return; |
| |
| namespace media_router { |
| |
| class MockMediaRouterAndroidBridge : public MediaRouterAndroidBridge { |
| public: |
| MockMediaRouterAndroidBridge() : MediaRouterAndroidBridge(nullptr) {} |
| ~MockMediaRouterAndroidBridge() override = default; |
| |
| MOCK_METHOD6(CreateRoute, |
| void(const MediaSource::Id&, |
| const MediaSink::Id&, |
| const std::string&, |
| const url::Origin&, |
| content::WebContents*, |
| int)); |
| MOCK_METHOD5(JoinRoute, |
| void(const MediaSource::Id&, |
| const std::string&, |
| const url::Origin&, |
| content::WebContents*, |
| int)); |
| MOCK_METHOD1(TerminateRoute, void(const MediaRoute::Id&)); |
| MOCK_METHOD2(SendRouteMessage, |
| void(const MediaRoute::Id&, const std::string&)); |
| MOCK_METHOD1(DetachRoute, void(const MediaRoute::Id&)); |
| MOCK_METHOD1(StartObservingMediaSinks, bool(const MediaSource::Id&)); |
| MOCK_METHOD1(StopObservingMediaSinks, void(const MediaSource::Id&)); |
| }; |
| |
| class MediaRouterAndroidTest : public testing::Test { |
| public: |
| void SetUp() override { |
| Java_TestMediaRouterClient_initialize(base::android::AttachCurrentThread()); |
| mock_bridge_ = new MockMediaRouterAndroidBridge(); |
| router_ = base::WrapUnique(new MediaRouterAndroid()); |
| router_->SetMediaRouterBridgeForTest(mock_bridge_); |
| } |
| |
| protected: |
| // For the checks that MediaRouter calls are running on the UI thread. |
| // Needs to be the first member variable to be destroyed last. |
| content::BrowserTaskEnvironment task_environment_; |
| |
| std::unique_ptr<MediaRouterAndroid> router_; |
| raw_ptr<MockMediaRouterAndroidBridge> mock_bridge_; |
| }; |
| |
| TEST_F(MediaRouterAndroidTest, DetachRoute) { |
| base::MockCallback<content::PresentationConnectionStateChangedCallback> |
| callback; |
| content::PresentationConnectionStateChangeInfo change_info_closed( |
| PresentationConnectionState::CLOSED); |
| change_info_closed.close_reason = |
| blink::mojom::PresentationConnectionCloseReason::CLOSED; |
| change_info_closed.message = "Route closed normally"; |
| EXPECT_CALL(callback, Run(StateChangeInfoEquals(change_info_closed))); |
| |
| Expectation createRouteExpectation = |
| EXPECT_CALL(*mock_bridge_, CreateRoute(_, _, _, _, _, 1)) |
| .WillOnce(Return()); |
| EXPECT_CALL(*mock_bridge_, DetachRoute("route")) |
| .After(createRouteExpectation) |
| .WillOnce(Return()); |
| |
| router_->CreateRoute("source", "sink", url::Origin(), nullptr, |
| base::DoNothing(), base::TimeDelta()); |
| router_->OnRouteCreated("route", "sink", 1, false); |
| |
| EXPECT_NE(nullptr, router_->FindRouteBySource("source")); |
| |
| base::CallbackListSubscription subscription = |
| router_->AddPresentationConnectionStateChangedCallback("route", |
| callback.Get()); |
| router_->DetachRoute("route"); |
| |
| EXPECT_EQ(nullptr, router_->FindRouteBySource("source")); |
| } |
| |
| TEST_F(MediaRouterAndroidTest, OnRouteTerminated) { |
| Expectation createRouteExpectation = |
| EXPECT_CALL(*mock_bridge_, CreateRoute(_, _, _, _, _, 1)) |
| .WillOnce(Return()); |
| |
| router_->CreateRoute("source", "sink", url::Origin(), nullptr, |
| base::DoNothing(), base::TimeDelta()); |
| router_->OnRouteCreated("route", "sink", 1, false); |
| |
| EXPECT_NE(nullptr, router_->FindRouteBySource("source")); |
| |
| // Route termination on Android results in the PresentationConnectionPtr |
| // directly being messaged, and therefore there is no |
| // PresentationConnectionStateChangedCallback that can be intercepted for |
| // test verification purposes. |
| router_->OnRouteTerminated("route"); |
| |
| EXPECT_EQ(nullptr, router_->FindRouteBySource("source")); |
| } |
| |
| TEST_F(MediaRouterAndroidTest, OnRouteClosed) { |
| base::MockCallback<content::PresentationConnectionStateChangedCallback> |
| callback; |
| content::PresentationConnectionStateChangeInfo change_info_closed( |
| PresentationConnectionState::CLOSED); |
| change_info_closed.close_reason = |
| blink::mojom::PresentationConnectionCloseReason::CLOSED; |
| change_info_closed.message = "Remove route"; |
| EXPECT_CALL(callback, Run(StateChangeInfoEquals(change_info_closed))); |
| |
| Expectation createRouteExpectation = |
| EXPECT_CALL(*mock_bridge_, CreateRoute(_, _, _, _, _, 1)) |
| .WillOnce(Return()); |
| |
| router_->CreateRoute("source", "sink", url::Origin(), nullptr, |
| base::DoNothing(), base::TimeDelta()); |
| router_->OnRouteCreated("route", "sink", 1, false); |
| |
| EXPECT_NE(nullptr, router_->FindRouteBySource("source")); |
| |
| base::CallbackListSubscription subscription = |
| router_->AddPresentationConnectionStateChangedCallback("route", |
| callback.Get()); |
| router_->OnRouteClosed("route", std::nullopt); |
| |
| EXPECT_EQ(nullptr, router_->FindRouteBySource("source")); |
| } |
| |
| TEST_F(MediaRouterAndroidTest, OnRouteClosedWithError) { |
| base::MockCallback<content::PresentationConnectionStateChangedCallback> |
| callback; |
| content::PresentationConnectionStateChangeInfo change_info_closed( |
| PresentationConnectionState::CLOSED); |
| change_info_closed.close_reason = |
| blink::mojom::PresentationConnectionCloseReason::CONNECTION_ERROR; |
| change_info_closed.message = "Some failure"; |
| EXPECT_CALL(callback, Run(StateChangeInfoEquals(change_info_closed))); |
| |
| Expectation createRouteExpectation = |
| EXPECT_CALL(*mock_bridge_, CreateRoute(_, _, _, _, _, 1)) |
| .WillOnce(Return()); |
| |
| router_->CreateRoute("source", "sink", url::Origin(), nullptr, |
| base::DoNothing(), base::TimeDelta()); |
| router_->OnRouteCreated("route", "sink", 1, false); |
| |
| EXPECT_NE(nullptr, router_->FindRouteBySource("source")); |
| |
| base::CallbackListSubscription subscription = |
| router_->AddPresentationConnectionStateChangedCallback("route", |
| callback.Get()); |
| router_->OnRouteClosed("route", "Some failure"); |
| |
| EXPECT_EQ(nullptr, router_->FindRouteBySource("source")); |
| } |
| |
| TEST_F(MediaRouterAndroidTest, OnRouteMediaSourceUpdated) { |
| const std::string route_id = "route-id"; |
| const std::string sink_id = "sink-id"; |
| const std::string source_id = "source-id"; |
| const std::string source_id2 = "source-id2"; |
| const url::Origin origin; |
| |
| EXPECT_CALL(*mock_bridge_, |
| CreateRoute(source_id, sink_id, _, origin, nullptr, 1)) |
| .WillOnce(Return()); |
| |
| router_->CreateRoute(source_id, sink_id, origin, nullptr, base::DoNothing(), |
| base::TimeDelta()); |
| router_->OnRouteCreated(route_id, sink_id, 1, false); |
| |
| EXPECT_NE(nullptr, router_->FindRouteBySource(source_id)); |
| EXPECT_EQ(nullptr, router_->FindRouteBySource(source_id2)); |
| |
| router_->OnRouteMediaSourceUpdated(route_id, source_id2); |
| |
| EXPECT_EQ(nullptr, router_->FindRouteBySource(source_id)); |
| EXPECT_NE(nullptr, router_->FindRouteBySource(source_id2)); |
| } |
| |
| } // namespace media_router |