| // Copyright 2020 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 "ash/display/privacy_screen_controller.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/dbus/privacy_screen_service_provider.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/callback_helpers.h" |
| #include "chromeos/ash/components/dbus/services/service_provider_test_helper.h" |
| #include "components/prefs/pref_service.h" |
| #include "dbus/message.h" |
| #include "dbus/object_path.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| #include "ui/display/fake/fake_display_snapshot.h" |
| #include "ui/display/manager/display_change_observer.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/manager/test/action_logger_util.h" |
| #include "ui/display/manager/test/test_native_display_delegate.h" |
| #include "ui/display/types/display_constants.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr char kUser1Email[] = "user1@privacyscreen"; |
| constexpr char kUser2Email[] = "user2@privacyscreen"; |
| constexpr gfx::Size kDisplaySize{1024, 768}; |
| |
| class MockObserver : public PrivacyScreenController::Observer { |
| public: |
| MockObserver() {} |
| ~MockObserver() override = default; |
| |
| MOCK_METHOD(void, |
| OnPrivacyScreenSettingChanged, |
| (bool enabled, bool notify_ui), |
| (override)); |
| }; |
| |
| class PrivacyScreenControllerTest : public NoSessionAshTestBase { |
| public: |
| PrivacyScreenControllerTest() |
| : logger_(std::make_unique<display::test::ActionLogger>()) {} |
| ~PrivacyScreenControllerTest() override = default; |
| |
| PrivacyScreenControllerTest(const PrivacyScreenControllerTest&) = delete; |
| PrivacyScreenControllerTest& operator=(const PrivacyScreenControllerTest&) = |
| delete; |
| |
| PrivacyScreenController* controller() { |
| return Shell::Get()->privacy_screen_controller(); |
| } |
| |
| PrefService* user1_pref_service() const { |
| return Shell::Get()->session_controller()->GetUserPrefServiceForUser( |
| AccountId::FromUserEmail(kUser1Email)); |
| } |
| |
| void SetUp() override { |
| NoSessionAshTestBase::SetUp(); |
| |
| // Create user 1 session and simulate its login. |
| SimulateUserLogin(kUser1Email); |
| |
| // Create user 2 session. |
| GetSessionControllerClient()->AddUserSession(kUser2Email); |
| |
| native_display_delegate_ = |
| new display::test::TestNativeDisplayDelegate(logger_.get()); |
| display_manager()->configurator()->SetDelegateForTesting( |
| std::unique_ptr<display::NativeDisplayDelegate>( |
| native_display_delegate_)); |
| display_change_observer_ = |
| std::make_unique<display::DisplayChangeObserver>(display_manager()); |
| test_api_ = std::make_unique<display::DisplayConfigurator::TestApi>( |
| display_manager()->configurator()); |
| |
| controller()->AddObserver(observer()); |
| } |
| |
| struct TestSnapshotParams { |
| int64_t id; |
| bool is_internal_display; |
| bool supports_privacy_screen; |
| }; |
| |
| void TearDown() override { |
| // DisplayChangeObserver access DeviceDataManager in its destructor, so |
| // destroy it first. |
| display_change_observer_ = nullptr; |
| controller()->RemoveObserver(observer()); |
| AshTestBase::TearDown(); |
| } |
| |
| void SwitchActiveUser(const std::string& email) { |
| GetSessionControllerClient()->SwitchActiveUser( |
| AccountId::FromUserEmail(email)); |
| } |
| |
| // Builds displays snapshots into |owned_snapshots_| and update the display |
| // configurator and display manager with it. |
| void BuildAndUpdateDisplaySnapshots( |
| const std::vector<TestSnapshotParams>& snapshot_params) { |
| owned_snapshots_.clear(); |
| std::vector<display::DisplaySnapshot*> outputs; |
| |
| for (const auto& param : snapshot_params) { |
| owned_snapshots_.emplace_back( |
| display::FakeDisplaySnapshot::Builder() |
| .SetId(param.id) |
| .SetNativeMode(kDisplaySize) |
| .SetCurrentMode(kDisplaySize) |
| .SetType(param.is_internal_display |
| ? display::DISPLAY_CONNECTION_TYPE_INTERNAL |
| : display::DISPLAY_CONNECTION_TYPE_HDMI) |
| .SetPrivacyScreen(param.supports_privacy_screen |
| ? display::kDisabled |
| : display::kNotSupported) |
| .Build()); |
| outputs.push_back(owned_snapshots_.back().get()); |
| } |
| |
| native_display_delegate_->set_outputs(outputs); |
| display_manager()->configurator()->OnConfigurationChanged(); |
| display_manager()->configurator()->ForceInitialConfigure(); |
| EXPECT_TRUE(test_api_->TriggerConfigureTimeout()); |
| display_change_observer_->OnDisplayModeChanged(outputs); |
| } |
| |
| MockObserver* observer() { return &observer_; } |
| |
| private: |
| std::unique_ptr<display::test::ActionLogger> logger_; |
| display::test::TestNativeDisplayDelegate* |
| native_display_delegate_; // Not owned. |
| std::unique_ptr<display::DisplayChangeObserver> display_change_observer_; |
| std::unique_ptr<display::DisplayConfigurator::TestApi> test_api_; |
| std::vector<std::unique_ptr<display::DisplaySnapshot>> owned_snapshots_; |
| ::testing::NiceMock<MockObserver> observer_; |
| }; |
| |
| class PrivacyScreenServiceProviderTest : public PrivacyScreenControllerTest { |
| public: |
| PrivacyScreenServiceProviderTest() = default; |
| ~PrivacyScreenServiceProviderTest() override = default; |
| PrivacyScreenServiceProviderTest(const PrivacyScreenServiceProviderTest&) = |
| delete; |
| PrivacyScreenServiceProviderTest& operator=( |
| const PrivacyScreenServiceProviderTest&) = delete; |
| |
| // PrivacyScreenControllerTest: |
| void SetUp() override { |
| PrivacyScreenControllerTest::SetUp(); |
| service_provider_ = std::make_unique<PrivacyScreenServiceProvider>(); |
| test_helper_.SetUp( |
| privacy_screen::kPrivacyScreenServiceName, |
| dbus::ObjectPath(privacy_screen::kPrivacyScreenServicePath), |
| privacy_screen::kPrivacyScreenServiceInterface, |
| privacy_screen::kPrivacyScreenServiceGetPrivacyScreenSettingMethod, |
| service_provider_.get()); |
| } |
| |
| void TearDown() override { |
| test_helper_.TearDown(); |
| service_provider_.reset(); |
| PrivacyScreenControllerTest::TearDown(); |
| } |
| |
| privacy_screen::PrivacyScreenSetting_PrivacyScreenState |
| GetPrivacyScreenSettingStateFromDBus() { |
| dbus::MethodCall method_call( |
| privacy_screen::kPrivacyScreenServiceInterface, |
| privacy_screen::kPrivacyScreenServiceGetPrivacyScreenSettingMethod); |
| std::unique_ptr<dbus::Response> response = |
| test_helper_.CallMethod(&method_call); |
| |
| dbus::MessageReader reader(response.get()); |
| privacy_screen::PrivacyScreenSetting setting; |
| EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&setting)); |
| EXPECT_FALSE(reader.HasMoreData()); |
| return setting.state(); |
| } |
| |
| void ConnectToPrivacyScreenSettingChangedDBusSignal() { |
| test_helper_.SetUpReturnSignal( |
| privacy_screen::kPrivacyScreenServiceInterface, |
| privacy_screen::kPrivacyScreenServicePrivacyScreenSettingChangedSignal, |
| base::BindRepeating(&PrivacyScreenServiceProviderTest:: |
| OnPrivacyScreenSettingChangedDBusSignal, |
| base::Unretained(this)), |
| base::DoNothing()); |
| } |
| |
| void OnPrivacyScreenSettingChangedDBusSignal(dbus::Signal* signal) { |
| dbus::MessageReader reader(signal); |
| privacy_screen::PrivacyScreenSetting setting; |
| EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&setting)); |
| last_signal_state_ = setting.state(); |
| EXPECT_FALSE(reader.HasMoreData()); |
| } |
| |
| protected: |
| privacy_screen::PrivacyScreenSetting_PrivacyScreenState last_signal_state_ = |
| privacy_screen::PrivacyScreenSetting_PrivacyScreenState_NOT_SUPPORTED; |
| std::unique_ptr<PrivacyScreenServiceProvider> service_provider_; |
| ServiceProviderTestHelper test_helper_; |
| }; |
| |
| // Test that user prefs do not get mixed up between user changes on a device |
| // with a single supporting display. |
| TEST_F(PrivacyScreenControllerTest, TestEnableAndDisable) { |
| // Create a single internal display that supports privacy screen. |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/true, |
| }}); |
| EXPECT_EQ(1u, display_manager()->GetNumDisplays()); |
| ASSERT_TRUE(controller()->IsSupported()); |
| |
| // Enable for user 1, and switch to user 2. User 2 should have it disabled. |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(true, true)); |
| controller()->SetEnabled(true, |
| PrivacyScreenController::kToggleUISurfaceCount); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| // Switching accounts should trigger observers but should not notify ui. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(false, false)); |
| SwitchActiveUser(kUser2Email); |
| EXPECT_FALSE(controller()->GetEnabled()); |
| |
| // Switch back to user 1, expect it to be enabled. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(true, false)); |
| SwitchActiveUser(kUser1Email); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| } |
| |
| // Checks that when the privacy screen is enforced by Data Leak Prevention |
| // feature, it's turned on regardless of the user pref state. |
| TEST_F(PrivacyScreenControllerTest, TestDlpEnforced) { |
| // Create a single internal display that supports privacy screen. |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/true, |
| }}); |
| EXPECT_EQ(1u, display_manager()->GetNumDisplays()); |
| ASSERT_TRUE(controller()->IsSupported()); |
| EXPECT_FALSE(controller()->GetEnabled()); |
| |
| // Enforce privacy screen and check notification. |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(true, true)); |
| controller()->SetEnforced(true); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| // Additionally enable it via pref, no change. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(true, true)); |
| controller()->SetEnabled(true, |
| PrivacyScreenController::kToggleUISurfaceCount); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| // Shouldn't be turned off when pref is disabled, because already enforced. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(true, true)); |
| controller()->SetEnabled(false, |
| PrivacyScreenController::kToggleUISurfaceCount); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| // Privacy screen enforced again by DLP, no notification should be shown. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), |
| OnPrivacyScreenSettingChanged(::testing::_, ::testing::_)) |
| .Times(0); |
| controller()->SetEnforced(true); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| // Remove enforcement, turned off as pref was not changed. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(false, true)); |
| controller()->SetEnforced(false); |
| EXPECT_FALSE(controller()->GetEnabled()); |
| |
| // Add pref back. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(true, true)); |
| controller()->SetEnabled(true, |
| PrivacyScreenController::kToggleUISurfaceCount); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| // Privacy screen enforced again by DLP, no notification should be shown as |
| // privacy screen already turned on by the user. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), |
| OnPrivacyScreenSettingChanged(::testing::_, ::testing::_)) |
| .Times(0); |
| controller()->SetEnforced(true); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| // Remove enforcement, privacy screen should still be on due to pref and no |
| // notification. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), |
| OnPrivacyScreenSettingChanged(::testing::_, ::testing::_)) |
| .Times(0); |
| controller()->SetEnforced(false); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| // Disable via pref, privacy screen is turned off with a notification. |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(false, true)); |
| controller()->SetEnabled(false, |
| PrivacyScreenController::kToggleUISurfaceCount); |
| EXPECT_FALSE(controller()->GetEnabled()); |
| } |
| |
| // Tests that updates of the Privacy Screen user prefs from outside the |
| // PrivacyScreenController (such as Settings UI) are observed and applied. |
| TEST_F(PrivacyScreenControllerTest, TestOutsidePrefsUpdates) { |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/true, |
| }}); |
| EXPECT_EQ(1u, display_manager()->GetNumDisplays()); |
| ASSERT_TRUE(controller()->IsSupported()); |
| |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(true, true)); |
| EXPECT_FALSE(controller()->GetEnabled()); |
| user1_pref_service()->SetBoolean(prefs::kDisplayPrivacyScreenEnabled, true); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| |
| ::testing::Mock::VerifyAndClear(observer()); |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(false, true)); |
| user1_pref_service()->SetBoolean(prefs::kDisplayPrivacyScreenEnabled, false); |
| EXPECT_FALSE(controller()->GetEnabled()); |
| } |
| |
| TEST_F(PrivacyScreenControllerTest, SupportedOnSingleInternalDisplay) { |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/true, |
| }}); |
| EXPECT_EQ(1u, display_manager()->GetNumDisplays()); |
| ASSERT_TRUE(controller()->IsSupported()); |
| |
| EXPECT_CALL(*observer(), OnPrivacyScreenSettingChanged(true, true)); |
| controller()->SetEnabled(true, |
| PrivacyScreenController::kToggleUISurfaceCount); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| } |
| |
| TEST_F(PrivacyScreenControllerTest, NotSupportedOnSingleInternalDisplay) { |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/false, |
| }}); |
| EXPECT_EQ(1u, display_manager()->GetNumDisplays()); |
| ASSERT_FALSE(controller()->IsSupported()); |
| |
| EXPECT_FALSE(controller()->GetEnabled()); |
| } |
| |
| // Test that the privacy screen is not supported when the device is connected |
| // to an external display and the lid is closed (a.k.a. docked mode). |
| TEST_F(PrivacyScreenControllerTest, NotSupportedOnInternalDisplayWhenDocked) { |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/true, |
| }, |
| { |
| /*id=*/234u, |
| /*is_internal_display=*/false, |
| /*supports_privacy_screen=*/false, |
| }}); |
| EXPECT_EQ(2u, display_manager()->GetNumDisplays()); |
| |
| // Turn off the internal display |
| display_manager()->configurator()->SetDisplayPower( |
| chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON, |
| display::DisplayConfigurator::kSetDisplayPowerNoFlags, base::DoNothing()); |
| |
| ASSERT_FALSE(controller()->IsSupported()); |
| EXPECT_FALSE(controller()->GetEnabled()); |
| } |
| |
| TEST_F(PrivacyScreenControllerTest, |
| SupportedOnInternalDisplayWithMultipleExternalDisplays) { |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/1234u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/true, |
| }, |
| { |
| /*id=*/2341u, |
| /*is_internal_display=*/false, |
| /*supports_privacy_screen=*/false, |
| }, |
| { |
| /*id=*/3412u, |
| /*is_internal_display=*/false, |
| /*supports_privacy_screen=*/false, |
| }}); |
| EXPECT_EQ(3u, display_manager()->GetNumDisplays()); |
| ASSERT_TRUE(controller()->IsSupported()); |
| |
| controller()->SetEnabled(true, |
| PrivacyScreenController::kToggleUISurfaceCount); |
| EXPECT_TRUE(controller()->GetEnabled()); |
| } |
| |
| TEST_F(PrivacyScreenControllerTest, |
| NotSupportedOnInternalDisplayWithMultipleExternalDisplays) { |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/1234u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/false, |
| }, |
| { |
| /*id=*/2341u, |
| /*is_internal_display=*/false, |
| /*supports_privacy_screen=*/false, |
| }, |
| { |
| /*id=*/3412u, |
| /*is_internal_display=*/false, |
| /*supports_privacy_screen=*/false, |
| }}); |
| EXPECT_EQ(3u, display_manager()->GetNumDisplays()); |
| ASSERT_FALSE(controller()->IsSupported()); |
| |
| EXPECT_FALSE(controller()->GetEnabled()); |
| } |
| |
| TEST_F(PrivacyScreenServiceProviderTest, PrivacyScreenNotSupported) { |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/false, |
| }}); |
| |
| ASSERT_EQ( |
| GetPrivacyScreenSettingStateFromDBus(), |
| privacy_screen::PrivacyScreenSetting_PrivacyScreenState_NOT_SUPPORTED); |
| } |
| |
| TEST_F(PrivacyScreenServiceProviderTest, PrivacyScreenDisabled) { |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/true, |
| }}); |
| |
| ASSERT_EQ(GetPrivacyScreenSettingStateFromDBus(), |
| privacy_screen::PrivacyScreenSetting_PrivacyScreenState_DISABLED); |
| } |
| |
| TEST_F(PrivacyScreenServiceProviderTest, PrivacyScreenEnabled) { |
| ConnectToPrivacyScreenSettingChangedDBusSignal(); |
| |
| BuildAndUpdateDisplaySnapshots({{ |
| /*id=*/123u, |
| /*is_internal_display=*/true, |
| /*supports_privacy_screen=*/true, |
| }}); |
| |
| controller()->SetEnabled(true, |
| PrivacyScreenController::kToggleUISurfaceCount); |
| |
| // Expects PrivacyScreenSettingChanged D-Bus signal to be called once. |
| ASSERT_EQ(last_signal_state_, |
| privacy_screen::PrivacyScreenSetting_PrivacyScreenState_ENABLED); |
| |
| ASSERT_EQ(GetPrivacyScreenSettingStateFromDBus(), |
| privacy_screen::PrivacyScreenSetting_PrivacyScreenState_ENABLED); |
| } |
| |
| } // namespace |
| |
| } // namespace ash |