| // Copyright 2017 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 "media/capture/video/chromeos/camera_hal_dispatcher_impl.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/posix/safe_strerror.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/test/task_environment.h" |
| #include "media/capture/video/chromeos/mojom/camera_common.mojom.h" |
| #include "media/capture/video/chromeos/mojom/cros_camera_client.mojom.h" |
| #include "media/capture/video/chromeos/mojom/cros_camera_service.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| using testing::InvokeWithoutArgs; |
| |
| namespace media { |
| namespace { |
| |
| class MockCameraHalServer : public cros::mojom::CameraHalServer { |
| public: |
| MockCameraHalServer() = default; |
| |
| MockCameraHalServer(const MockCameraHalServer&) = delete; |
| MockCameraHalServer& operator=(const MockCameraHalServer&) = delete; |
| |
| ~MockCameraHalServer() = default; |
| |
| void CreateChannel( |
| mojo::PendingReceiver<cros::mojom::CameraModule> camera_module_receiver, |
| cros::mojom::CameraClientType camera_client_type) override { |
| DoCreateChannel(std::move(camera_module_receiver), camera_client_type); |
| } |
| MOCK_METHOD2(DoCreateChannel, |
| void(mojo::PendingReceiver<cros::mojom::CameraModule> |
| camera_module_receiver, |
| cros::mojom::CameraClientType camera_client_type)); |
| |
| MOCK_METHOD1(SetTracingEnabled, void(bool enabled)); |
| |
| mojo::PendingRemote<cros::mojom::CameraHalServer> GetPendingRemote() { |
| return receiver_.BindNewPipeAndPassRemote(); |
| } |
| |
| private: |
| mojo::Receiver<cros::mojom::CameraHalServer> receiver_{this}; |
| }; |
| |
| class MockCameraHalClient : public cros::mojom::CameraHalClient { |
| public: |
| MockCameraHalClient() = default; |
| |
| MockCameraHalClient(const MockCameraHalClient&) = delete; |
| MockCameraHalClient& operator=(const MockCameraHalClient&) = delete; |
| |
| ~MockCameraHalClient() = default; |
| |
| void SetUpChannel( |
| mojo::PendingRemote<cros::mojom::CameraModule> camera_module) override { |
| DoSetUpChannel(std::move(camera_module)); |
| } |
| MOCK_METHOD1( |
| DoSetUpChannel, |
| void(mojo::PendingRemote<cros::mojom::CameraModule> camera_module)); |
| |
| mojo::PendingRemote<cros::mojom::CameraHalClient> GetPendingRemote() { |
| return receiver_.BindNewPipeAndPassRemote(); |
| } |
| |
| private: |
| mojo::Receiver<cros::mojom::CameraHalClient> receiver_{this}; |
| }; |
| |
| class MockCameraActiveClientObserver : public CameraActiveClientObserver { |
| public: |
| void OnActiveClientChange(cros::mojom::CameraClientType type, |
| bool is_active) override { |
| DoOnActiveClientChange(type, is_active); |
| } |
| MOCK_METHOD2(DoOnActiveClientChange, |
| void(cros::mojom::CameraClientType, bool)); |
| }; |
| |
| } // namespace |
| |
| class CameraHalDispatcherImplTest : public ::testing::Test { |
| public: |
| CameraHalDispatcherImplTest() |
| : register_client_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC) {} |
| |
| CameraHalDispatcherImplTest(const CameraHalDispatcherImplTest&) = delete; |
| CameraHalDispatcherImplTest& operator=(const CameraHalDispatcherImplTest&) = |
| delete; |
| |
| ~CameraHalDispatcherImplTest() override = default; |
| |
| void SetUp() override { |
| dispatcher_ = new CameraHalDispatcherImpl(); |
| EXPECT_TRUE(dispatcher_->StartThreads()); |
| } |
| |
| void TearDown() override { delete dispatcher_; } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> GetProxyTaskRunner() { |
| return dispatcher_->proxy_task_runner_; |
| } |
| |
| void DoLoop() { |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| } |
| |
| void QuitRunLoop() { |
| if (run_loop_) { |
| run_loop_->Quit(); |
| } |
| } |
| |
| static void RegisterServer( |
| CameraHalDispatcherImpl* dispatcher, |
| mojo::PendingRemote<cros::mojom::CameraHalServer> server, |
| cros::mojom::CameraHalDispatcher::RegisterServerWithTokenCallback |
| callback) { |
| auto token = base::UnguessableToken::Create(); |
| dispatcher->GetTokenManagerForTesting()->AssignServerTokenForTesting(token); |
| dispatcher->RegisterServerWithToken(std::move(server), std::move(token), |
| std::move(callback)); |
| } |
| |
| static void RegisterClientWithToken( |
| CameraHalDispatcherImpl* dispatcher, |
| mojo::PendingRemote<cros::mojom::CameraHalClient> client, |
| cros::mojom::CameraClientType type, |
| const base::UnguessableToken& token, |
| cros::mojom::CameraHalDispatcher::RegisterClientWithTokenCallback |
| callback) { |
| dispatcher->RegisterClientWithToken(std::move(client), type, token, |
| std::move(callback)); |
| } |
| |
| void OnRegisteredServer( |
| int32_t result, |
| mojo::PendingRemote<cros::mojom::CameraHalServerCallbacks> callbacks) { |
| if (result != 0) { |
| ADD_FAILURE() << "Failed to register server: " |
| << base::safe_strerror(-result); |
| QuitRunLoop(); |
| } |
| } |
| |
| void OnRegisteredClient(int32_t result) { |
| last_register_client_result_ = result; |
| if (result != 0) { |
| // If registration fails, CameraHalClient::SetUpChannel() will not be |
| // called, and we need to quit the run loop here. |
| QuitRunLoop(); |
| } |
| register_client_event_.Signal(); |
| } |
| |
| protected: |
| // We can't use std::unique_ptr here because the constructor and destructor of |
| // CameraHalDispatcherImpl are private. |
| CameraHalDispatcherImpl* dispatcher_; |
| base::WaitableEvent register_client_event_; |
| int32_t last_register_client_result_; |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| }; |
| |
| // Test that the CameraHalDisptcherImpl correctly re-establishes a Mojo channel |
| // for the client when the server crashes. |
| TEST_F(CameraHalDispatcherImplTest, ServerConnectionError) { |
| // First verify that a the CameraHalDispatcherImpl establishes a Mojo channel |
| // between the server and the client. |
| auto mock_server = std::make_unique<MockCameraHalServer>(); |
| auto mock_client = std::make_unique<MockCameraHalClient>(); |
| |
| EXPECT_CALL(*mock_server, DoCreateChannel(_, _)).Times(1); |
| EXPECT_CALL(*mock_client, DoSetUpChannel(_)) |
| .Times(1) |
| .WillOnce( |
| InvokeWithoutArgs(this, &CameraHalDispatcherImplTest::QuitRunLoop)); |
| |
| auto server = mock_server->GetPendingRemote(); |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterServer, |
| base::Unretained(dispatcher_), std::move(server), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredServer, |
| base::Unretained(this)))); |
| auto client = mock_client->GetPendingRemote(); |
| auto type = cros::mojom::CameraClientType::TESTING; |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterClientWithToken, |
| base::Unretained(dispatcher_), std::move(client), type, |
| dispatcher_->GetTokenForTrustedClient(type), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredClient, |
| base::Unretained(this)))); |
| |
| // Wait until the client gets the established Mojo channel. |
| DoLoop(); |
| |
| // The client registration callback may be called after |
| // CameraHalClient::SetUpChannel(). Use a waitable event to make sure we have |
| // the result. |
| register_client_event_.Wait(); |
| ASSERT_EQ(last_register_client_result_, 0); |
| |
| // Re-create a new server to simulate a server crash. |
| mock_server = std::make_unique<MockCameraHalServer>(); |
| |
| // Make sure we creates a new Mojo channel from the new server to the same |
| // client. |
| EXPECT_CALL(*mock_server, DoCreateChannel(_, _)).Times(1); |
| EXPECT_CALL(*mock_client, DoSetUpChannel(_)) |
| .Times(1) |
| .WillOnce( |
| InvokeWithoutArgs(this, &CameraHalDispatcherImplTest::QuitRunLoop)); |
| |
| server = mock_server->GetPendingRemote(); |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterServer, |
| base::Unretained(dispatcher_), std::move(server), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredServer, |
| base::Unretained(this)))); |
| |
| // Wait until the clients gets the newly established Mojo channel. |
| DoLoop(); |
| } |
| |
| // Test that the CameraHalDisptcherImpl correctly re-establishes a Mojo channel |
| // for the client when the client reconnects after crash. |
| TEST_F(CameraHalDispatcherImplTest, ClientConnectionError) { |
| // First verify that a the CameraHalDispatcherImpl establishes a Mojo channel |
| // between the server and the client. |
| auto mock_server = std::make_unique<MockCameraHalServer>(); |
| auto mock_client = std::make_unique<MockCameraHalClient>(); |
| |
| EXPECT_CALL(*mock_server, DoCreateChannel(_, _)).Times(1); |
| EXPECT_CALL(*mock_client, DoSetUpChannel(_)) |
| .Times(1) |
| .WillOnce( |
| InvokeWithoutArgs(this, &CameraHalDispatcherImplTest::QuitRunLoop)); |
| |
| auto server = mock_server->GetPendingRemote(); |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterServer, |
| base::Unretained(dispatcher_), std::move(server), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredServer, |
| base::Unretained(this)))); |
| auto client = mock_client->GetPendingRemote(); |
| auto type = cros::mojom::CameraClientType::TESTING; |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterClientWithToken, |
| base::Unretained(dispatcher_), std::move(client), type, |
| dispatcher_->GetTokenForTrustedClient(type), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredClient, |
| base::Unretained(this)))); |
| |
| // Wait until the client gets the established Mojo channel. |
| DoLoop(); |
| |
| // The client registration callback may be called after |
| // CameraHalClient::SetUpChannel(). Use a waitable event to make sure we have |
| // the result. |
| register_client_event_.Wait(); |
| ASSERT_EQ(last_register_client_result_, 0); |
| |
| // Re-create a new client to simulate a client crash. |
| mock_client = std::make_unique<MockCameraHalClient>(); |
| |
| // Make sure we re-create the Mojo channel from the same server to the new |
| // client. |
| EXPECT_CALL(*mock_server, DoCreateChannel(_, _)).Times(1); |
| EXPECT_CALL(*mock_client, DoSetUpChannel(_)) |
| .Times(1) |
| .WillOnce( |
| InvokeWithoutArgs(this, &CameraHalDispatcherImplTest::QuitRunLoop)); |
| |
| client = mock_client->GetPendingRemote(); |
| type = cros::mojom::CameraClientType::TESTING; |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterClientWithToken, |
| base::Unretained(dispatcher_), std::move(client), type, |
| dispatcher_->GetTokenForTrustedClient(type), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredClient, |
| base::Unretained(this)))); |
| |
| // Wait until the clients gets the newly established Mojo channel. |
| DoLoop(); |
| |
| // Make sure the client is still successfully registered. |
| register_client_event_.Wait(); |
| ASSERT_EQ(last_register_client_result_, 0); |
| } |
| |
| // Test that trusted camera HAL clients (e.g., Chrome, Android, Testing) can be |
| // registered successfully. |
| TEST_F(CameraHalDispatcherImplTest, RegisterClientSuccess) { |
| // First verify that a the CameraHalDispatcherImpl establishes a Mojo channel |
| // between the server and the client. |
| auto mock_server = std::make_unique<MockCameraHalServer>(); |
| |
| auto server = mock_server->GetPendingRemote(); |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterServer, |
| base::Unretained(dispatcher_), std::move(server), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredServer, |
| base::Unretained(this)))); |
| |
| for (auto type : TokenManager::kTrustedClientTypes) { |
| auto mock_client = std::make_unique<MockCameraHalClient>(); |
| EXPECT_CALL(*mock_server, DoCreateChannel(_, _)).Times(1); |
| EXPECT_CALL(*mock_client, DoSetUpChannel(_)) |
| .Times(1) |
| .WillOnce( |
| InvokeWithoutArgs(this, &CameraHalDispatcherImplTest::QuitRunLoop)); |
| |
| auto client = mock_client->GetPendingRemote(); |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterClientWithToken, |
| base::Unretained(dispatcher_), std::move(client), type, |
| dispatcher_->GetTokenForTrustedClient(type), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredClient, |
| base::Unretained(this)))); |
| |
| // Wait until the client gets the established Mojo channel. |
| DoLoop(); |
| |
| // The client registration callback may be called after |
| // CameraHalClient::SetUpChannel(). Use a waitable event to make sure we |
| // have the result. |
| register_client_event_.Wait(); |
| ASSERT_EQ(last_register_client_result_, 0); |
| } |
| } |
| |
| // Test that CameraHalClient registration fails when a wrong (empty) token is |
| // provided. |
| TEST_F(CameraHalDispatcherImplTest, RegisterClientFail) { |
| // First verify that a the CameraHalDispatcherImpl establishes a Mojo channel |
| // between the server and the client. |
| auto mock_server = std::make_unique<MockCameraHalServer>(); |
| auto mock_client = std::make_unique<MockCameraHalClient>(); |
| |
| auto server = mock_server->GetPendingRemote(); |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterServer, |
| base::Unretained(dispatcher_), std::move(server), |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredServer, |
| base::Unretained(this)))); |
| |
| // Use an empty token to make sure authentication fails. |
| base::UnguessableToken empty_token; |
| auto client = mock_client->GetPendingRemote(); |
| GetProxyTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CameraHalDispatcherImplTest::RegisterClientWithToken, |
| base::Unretained(dispatcher_), std::move(client), |
| cros::mojom::CameraClientType::TESTING, empty_token, |
| base::BindOnce(&CameraHalDispatcherImplTest::OnRegisteredClient, |
| base::Unretained(this)))); |
| |
| // We do not need to enter a run loop here because |
| // CameraHalClient::SetUpChannel() isn't expected to called, and we only need |
| // to wait for the callback from CameraHalDispatcher::RegisterClientWithToken. |
| register_client_event_.Wait(); |
| ASSERT_EQ(last_register_client_result_, -EPERM); |
| } |
| |
| // Test that CameraHalDispatcherImpl correctly fires CameraActiveClientObserver |
| // when a camera device is opened or closed by a client. |
| TEST_F(CameraHalDispatcherImplTest, CameraActiveClientObserverTest) { |
| MockCameraActiveClientObserver observer; |
| dispatcher_->AddActiveClientObserver(&observer); |
| |
| EXPECT_CALL(observer, DoOnActiveClientChange( |
| cros::mojom::CameraClientType::TESTING, true)) |
| .Times(1) |
| .WillOnce( |
| InvokeWithoutArgs(this, &CameraHalDispatcherImplTest::QuitRunLoop)); |
| dispatcher_->CameraDeviceActivityChange( |
| /*camera_id=*/0, /*opened=*/true, cros::mojom::CameraClientType::TESTING); |
| |
| DoLoop(); |
| |
| EXPECT_CALL(observer, DoOnActiveClientChange( |
| cros::mojom::CameraClientType::TESTING, false)) |
| .Times(1) |
| .WillOnce( |
| InvokeWithoutArgs(this, &CameraHalDispatcherImplTest::QuitRunLoop)); |
| dispatcher_->CameraDeviceActivityChange( |
| /*camera_id=*/0, /*opened=*/false, |
| cros::mojom::CameraClientType::TESTING); |
| |
| DoLoop(); |
| } |
| |
| } // namespace media |