| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/session/session_controller_impl.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/login_status.h" |
| #include "ash/public/cpp/ash_prefs.h" |
| #include "ash/public/cpp/session/scoped_screen_lock_blocker.h" |
| #include "ash/public/cpp/session/session_observer.h" |
| #include "ash/session/test_session_controller_client.h" |
| #include "ash/shell.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/json/values_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/bind.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/user_manager/user_type.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/window.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/window/dialog_delegate.h" |
| |
| using session_manager::SessionState; |
| |
| namespace ash { |
| namespace { |
| |
| class TestSessionObserver : public SessionObserver { |
| public: |
| TestSessionObserver() : active_account_id_(EmptyAccountId()) {} |
| |
| TestSessionObserver(const TestSessionObserver&) = delete; |
| TestSessionObserver& operator=(const TestSessionObserver&) = delete; |
| |
| ~TestSessionObserver() override = default; |
| |
| // SessionObserver: |
| void OnActiveUserSessionChanged(const AccountId& account_id) override { |
| active_account_id_ = account_id; |
| } |
| |
| void OnUserSessionAdded(const AccountId& account_id) override { |
| user_session_account_ids_.push_back(account_id); |
| } |
| |
| void OnFirstSessionStarted() override { first_session_started_ = true; } |
| |
| void OnSessionStateChanged(SessionState state) override { state_ = state; } |
| |
| void OnActiveUserPrefServiceChanged(PrefService* pref_service) override { |
| DCHECK_NE(last_user_pref_service_, pref_service); |
| last_user_pref_service_ = pref_service; |
| ++user_prefs_changed_count_; |
| } |
| |
| std::string GetUserSessionEmails() const { |
| std::string emails; |
| for (const auto& account_id : user_session_account_ids_) { |
| emails += account_id.GetUserEmail() + ","; |
| } |
| return emails; |
| } |
| |
| SessionState state() const { return state_; } |
| const AccountId& active_account_id() const { return active_account_id_; } |
| bool first_session_started() const { return first_session_started_; } |
| const std::vector<AccountId>& user_session_account_ids() const { |
| return user_session_account_ids_; |
| } |
| PrefService* last_user_pref_service() const { |
| return last_user_pref_service_; |
| } |
| void clear_last_user_pref_service() { last_user_pref_service_ = nullptr; } |
| int user_prefs_changed_count() const { return user_prefs_changed_count_; } |
| |
| private: |
| SessionState state_ = SessionState::UNKNOWN; |
| AccountId active_account_id_; |
| bool first_session_started_ = false; |
| std::vector<AccountId> user_session_account_ids_; |
| raw_ptr<PrefService> last_user_pref_service_ = nullptr; |
| int user_prefs_changed_count_ = 0; |
| }; |
| |
| void FillDefaultSessionInfo(SessionInfo* info) { |
| info->can_lock_screen = true; |
| info->should_lock_screen_automatically = true; |
| info->is_running_in_app_mode = false; |
| info->add_user_session_policy = AddUserSessionPolicy::ALLOWED; |
| info->state = SessionState::LOGIN_PRIMARY; |
| } |
| |
| class SessionControllerImplTest : public testing::Test { |
| public: |
| SessionControllerImplTest() = default; |
| |
| SessionControllerImplTest(const SessionControllerImplTest&) = delete; |
| SessionControllerImplTest& operator=(const SessionControllerImplTest&) = |
| delete; |
| |
| ~SessionControllerImplTest() override = default; |
| |
| // testing::Test: |
| void SetUp() override { |
| controller_ = std::make_unique<SessionControllerImpl>(); |
| controller_->AddObserver(&observer_); |
| } |
| |
| void TearDown() override { controller_->RemoveObserver(&observer_); } |
| |
| void SetSessionInfo(const SessionInfo& info) { |
| controller_->SetSessionInfo(info); |
| } |
| |
| void UpdateSession(uint32_t session_id, const std::string& email) { |
| UserSession session; |
| session.session_id = session_id; |
| session.user_info.type = user_manager::UserType::kRegular; |
| session.user_info.account_id = AccountId::FromUserEmail(email); |
| session.user_info.display_name = email; |
| session.user_info.display_email = email; |
| session.user_info.is_new_profile = false; |
| |
| controller_->UpdateUserSession(session); |
| } |
| |
| std::string GetUserSessionEmails() const { |
| std::string emails; |
| for (const auto& session : controller_->GetUserSessions()) { |
| emails += session->user_info.display_email + ","; |
| } |
| return emails; |
| } |
| |
| SessionControllerImpl* controller() { return controller_.get(); } |
| const TestSessionObserver* observer() const { return &observer_; } |
| |
| private: |
| std::unique_ptr<SessionControllerImpl> controller_; |
| TestSessionObserver observer_; |
| }; |
| |
| class SessionControllerImplWithShellTest : public AshTestBase { |
| public: |
| SessionControllerImplWithShellTest() = default; |
| |
| SessionControllerImplWithShellTest( |
| const SessionControllerImplWithShellTest&) = delete; |
| SessionControllerImplWithShellTest& operator=( |
| const SessionControllerImplWithShellTest&) = delete; |
| |
| ~SessionControllerImplWithShellTest() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| controller()->AddObserver(&observer_); |
| } |
| |
| void TearDown() override { |
| controller()->RemoveObserver(&observer_); |
| window_.reset(); |
| AshTestBase::TearDown(); |
| } |
| |
| void CreateFullscreenWindow() { |
| window_ = CreateTestWindow(); |
| window_->SetProperty(aura::client::kShowStateKey, |
| ui::SHOW_STATE_FULLSCREEN); |
| window_state_ = WindowState::Get(window_.get()); |
| } |
| |
| SessionControllerImpl* controller() { |
| return Shell::Get()->session_controller(); |
| } |
| const TestSessionObserver* observer() const { return &observer_; } |
| |
| protected: |
| raw_ptr<WindowState, DanglingUntriaged> window_state_ = nullptr; |
| |
| private: |
| TestSessionObserver observer_; |
| std::unique_ptr<aura::Window> window_; |
| }; |
| |
| // Tests that the simple session info is reflected properly. |
| TEST_F(SessionControllerImplTest, SimpleSessionInfo) { |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| SetSessionInfo(info); |
| UpdateSession(1u, "user1@test.com"); |
| |
| EXPECT_TRUE(controller()->CanLockScreen()); |
| EXPECT_TRUE(controller()->ShouldLockScreenAutomatically()); |
| EXPECT_FALSE(controller()->IsRunningInAppMode()); |
| |
| info.can_lock_screen = false; |
| SetSessionInfo(info); |
| EXPECT_FALSE(controller()->CanLockScreen()); |
| EXPECT_TRUE(controller()->ShouldLockScreenAutomatically()); |
| EXPECT_FALSE(controller()->IsRunningInAppMode()); |
| |
| info.should_lock_screen_automatically = false; |
| SetSessionInfo(info); |
| EXPECT_FALSE(controller()->CanLockScreen()); |
| EXPECT_FALSE(controller()->ShouldLockScreenAutomatically()); |
| EXPECT_FALSE(controller()->IsRunningInAppMode()); |
| |
| info.is_running_in_app_mode = true; |
| SetSessionInfo(info); |
| EXPECT_FALSE(controller()->CanLockScreen()); |
| EXPECT_FALSE(controller()->ShouldLockScreenAutomatically()); |
| EXPECT_TRUE(controller()->IsRunningInAppMode()); |
| } |
| |
| TEST_F(SessionControllerImplTest, OnFirstSessionStarted) { |
| // Simulate chrome starting a user session. |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| SetSessionInfo(info); |
| UpdateSession(1u, "user1@test.com"); |
| controller()->SetUserSessionOrder({1u}); |
| |
| // Observer is notified. |
| EXPECT_TRUE(observer()->first_session_started()); |
| } |
| |
| // Tests that the CanLockScreen is only true with an active user session. |
| TEST_F(SessionControllerImplTest, CanLockScreen) { |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| ASSERT_TRUE(info.can_lock_screen); // Check can_lock_screen default to true. |
| SetSessionInfo(info); |
| |
| // Cannot lock screen when there is no active user session. |
| EXPECT_FALSE(controller()->IsActiveUserSessionStarted()); |
| EXPECT_FALSE(controller()->CanLockScreen()); |
| |
| UpdateSession(1u, "user1@test.com"); |
| EXPECT_TRUE(controller()->IsActiveUserSessionStarted()); |
| EXPECT_TRUE(controller()->CanLockScreen()); |
| } |
| |
| // Tests that AddUserSessionPolicy is set properly. |
| TEST_F(SessionControllerImplTest, AddUserPolicy) { |
| const AddUserSessionPolicy kTestCases[] = { |
| AddUserSessionPolicy::ALLOWED, |
| AddUserSessionPolicy::ERROR_NOT_ALLOWED_PRIMARY_USER, |
| AddUserSessionPolicy::ERROR_NO_ELIGIBLE_USERS, |
| AddUserSessionPolicy::ERROR_MAXIMUM_USERS_REACHED, |
| }; |
| |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| for (const auto& policy : kTestCases) { |
| info.add_user_session_policy = policy; |
| SetSessionInfo(info); |
| EXPECT_EQ(policy, controller()->GetAddUserPolicy()) |
| << "Test case policy=" << static_cast<int>(policy); |
| } |
| } |
| |
| // Tests that session state can be set and reflected properly. |
| TEST_F(SessionControllerImplWithShellTest, SessionState) { |
| const struct { |
| SessionState state; |
| bool expected_is_screen_locked; |
| bool expected_is_user_session_blocked; |
| } kTestCases[] = { |
| {SessionState::OOBE, false, true}, |
| {SessionState::LOGIN_PRIMARY, false, true}, |
| {SessionState::LOGGED_IN_NOT_ACTIVE, false, false}, |
| {SessionState::ACTIVE, false, false}, |
| {SessionState::LOCKED, true, true}, |
| {SessionState::LOGIN_SECONDARY, false, true}, |
| }; |
| |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| for (const auto& test_case : kTestCases) { |
| info.state = test_case.state; |
| controller()->SetSessionInfo(info); |
| |
| EXPECT_EQ(test_case.state, controller()->GetSessionState()) |
| << "Test case state=" << static_cast<int>(test_case.state); |
| EXPECT_EQ(observer()->state(), controller()->GetSessionState()) |
| << "Test case state=" << static_cast<int>(test_case.state); |
| EXPECT_EQ(test_case.expected_is_screen_locked, |
| controller()->IsScreenLocked()) |
| << "Test case state=" << static_cast<int>(test_case.state); |
| EXPECT_EQ(test_case.expected_is_user_session_blocked, |
| controller()->IsUserSessionBlocked()) |
| << "Test case state=" << static_cast<int>(test_case.state); |
| } |
| } |
| |
| // Tests that LoginStatus is computed correctly for most session states. |
| TEST_F(SessionControllerImplTest, GetLoginStatus) { |
| const struct { |
| SessionState state; |
| LoginStatus expected_status; |
| } kTestCases[] = { |
| {SessionState::UNKNOWN, LoginStatus::NOT_LOGGED_IN}, |
| {SessionState::OOBE, LoginStatus::NOT_LOGGED_IN}, |
| {SessionState::LOGIN_PRIMARY, LoginStatus::NOT_LOGGED_IN}, |
| {SessionState::LOGGED_IN_NOT_ACTIVE, LoginStatus::NOT_LOGGED_IN}, |
| {SessionState::LOCKED, LoginStatus::LOCKED}, |
| // TODO(jamescook): Add LOGIN_SECONDARY if we added a status for it. |
| }; |
| |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| for (const auto& test_case : kTestCases) { |
| info.state = test_case.state; |
| SetSessionInfo(info); |
| EXPECT_EQ(test_case.expected_status, controller()->login_status()) |
| << "Test case state=" << static_cast<int>(test_case.state); |
| } |
| } |
| |
| // Tests that LoginStatus is computed correctly for active sessions. |
| TEST_F(SessionControllerImplTest, GetLoginStateForActiveSession) { |
| // Simulate an active user session. |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| info.state = SessionState::ACTIVE; |
| SetSessionInfo(info); |
| |
| const struct { |
| user_manager::UserType user_type; |
| LoginStatus expected_status; |
| } kTestCases[] = { |
| {user_manager::UserType::kRegular, LoginStatus::USER}, |
| {user_manager::UserType::kGuest, LoginStatus::GUEST}, |
| {user_manager::UserType::kPublicAccount, LoginStatus::PUBLIC}, |
| {user_manager::UserType::kKioskApp, LoginStatus::KIOSK_APP}, |
| {user_manager::UserType::kChild, LoginStatus::CHILD}, |
| {user_manager::UserType::kArcKioskApp, LoginStatus::KIOSK_APP}, |
| {user_manager::UserType::kWebKioskApp, LoginStatus::KIOSK_APP} |
| }; |
| |
| for (const auto& test_case : kTestCases) { |
| UserSession session; |
| session.session_id = 1u; |
| session.user_info.type = test_case.user_type; |
| session.user_info.account_id = AccountId::FromUserEmail("user1@test.com"); |
| session.user_info.display_name = "User 1"; |
| session.user_info.display_email = "user1@test.com"; |
| controller()->UpdateUserSession(session); |
| |
| EXPECT_EQ(test_case.expected_status, controller()->login_status()) |
| << "Test case user_type=" << static_cast<int>(test_case.user_type); |
| } |
| } |
| |
| // Tests that user sessions can be set and updated. |
| TEST_F(SessionControllerImplTest, UserSessions) { |
| EXPECT_FALSE(controller()->IsActiveUserSessionStarted()); |
| |
| UpdateSession(1u, "user1@test.com"); |
| EXPECT_TRUE(controller()->IsActiveUserSessionStarted()); |
| EXPECT_EQ("user1@test.com,", GetUserSessionEmails()); |
| EXPECT_EQ(GetUserSessionEmails(), observer()->GetUserSessionEmails()); |
| EXPECT_EQ("user1@test.com", |
| controller()->GetPrimaryUserSession()->user_info.display_email); |
| |
| UpdateSession(2u, "user2@test.com"); |
| EXPECT_TRUE(controller()->IsActiveUserSessionStarted()); |
| EXPECT_EQ("user1@test.com,user2@test.com,", GetUserSessionEmails()); |
| EXPECT_EQ(GetUserSessionEmails(), observer()->GetUserSessionEmails()); |
| EXPECT_EQ("user1@test.com", |
| controller()->GetPrimaryUserSession()->user_info.display_email); |
| |
| UpdateSession(1u, "user1_changed@test.com"); |
| EXPECT_EQ("user1_changed@test.com,user2@test.com,", GetUserSessionEmails()); |
| // TODO(xiyuan): Verify observer gets the updated user session info. |
| } |
| |
| // Tests that user sessions can be ordered. |
| TEST_F(SessionControllerImplTest, ActiveSession) { |
| UpdateSession(1u, "user1@test.com"); |
| UpdateSession(2u, "user2@test.com"); |
| EXPECT_EQ("user1@test.com", |
| controller()->GetPrimaryUserSession()->user_info.display_email); |
| |
| std::vector<uint32_t> order = {1u, 2u}; |
| controller()->SetUserSessionOrder(order); |
| EXPECT_EQ("user1@test.com,user2@test.com,", GetUserSessionEmails()); |
| EXPECT_EQ("user1@test.com", observer()->active_account_id().GetUserEmail()); |
| EXPECT_EQ("user1@test.com", |
| controller()->GetPrimaryUserSession()->user_info.display_email); |
| |
| order = {2u, 1u}; |
| controller()->SetUserSessionOrder(order); |
| EXPECT_EQ("user2@test.com,user1@test.com,", GetUserSessionEmails()); |
| EXPECT_EQ("user2@test.com", observer()->active_account_id().GetUserEmail()); |
| EXPECT_EQ("user1@test.com", |
| controller()->GetPrimaryUserSession()->user_info.display_email); |
| |
| order = {1u, 2u}; |
| controller()->SetUserSessionOrder(order); |
| EXPECT_EQ("user1@test.com,user2@test.com,", GetUserSessionEmails()); |
| EXPECT_EQ("user1@test.com", observer()->active_account_id().GetUserEmail()); |
| EXPECT_EQ("user1@test.com", |
| controller()->GetPrimaryUserSession()->user_info.display_email); |
| } |
| |
| // Tests that user session is unblocked with a running unlock animation so that |
| // focus rules can find a correct activatable window after screen lock is |
| // dismissed. |
| TEST_F(SessionControllerImplWithShellTest, |
| UserSessionUnblockedWithRunningUnlockAnimation) { |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| |
| // LOCKED means blocked user session. |
| info.state = SessionState::LOCKED; |
| controller()->SetSessionInfo(info); |
| EXPECT_TRUE(controller()->IsUserSessionBlocked()); |
| |
| const struct { |
| SessionState state; |
| bool expect_blocked_after_unlock_animation; |
| } kTestCases[] = { |
| {SessionState::LOCKED, false}, |
| {SessionState::OOBE, true}, |
| {SessionState::LOGIN_PRIMARY, true}, |
| {SessionState::LOGGED_IN_NOT_ACTIVE, false}, |
| {SessionState::ACTIVE, false}, |
| {SessionState::LOGIN_SECONDARY, true}, |
| }; |
| for (const auto& test_case : kTestCases) { |
| info.state = test_case.state; |
| controller()->SetSessionInfo(info); |
| |
| // Mark a running unlock animation. |
| base::RunLoop run_loop; |
| controller()->RunUnlockAnimation(base::BindLambdaForTesting( |
| [&run_loop](bool aborted) { run_loop.Quit(); })); |
| run_loop.Run(); |
| EXPECT_EQ(test_case.expect_blocked_after_unlock_animation, |
| controller()->IsUserSessionBlocked()) |
| << "Test case state=" << static_cast<int>(test_case.state); |
| } |
| } |
| |
| TEST_F(SessionControllerImplTest, IsUserChild) { |
| UserSession session; |
| session.session_id = 1u; |
| session.user_info.type = user_manager::UserType::kChild; |
| controller()->UpdateUserSession(session); |
| |
| EXPECT_TRUE(controller()->IsUserChild()); |
| } |
| |
| class SessionControllerImplPrefsTest : public NoSessionAshTestBase { |
| public: |
| SessionControllerImplPrefsTest() |
| : NoSessionAshTestBase( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| }; |
| |
| // Verifies that SessionObserver is notified for PrefService changes. |
| TEST_F(SessionControllerImplPrefsTest, Observer) { |
| constexpr char kUser1[] = "user1@test.com"; |
| constexpr char kUser2[] = "user2@test.com"; |
| const AccountId kUserAccount1 = AccountId::FromUserEmail(kUser1); |
| const AccountId kUserAccount2 = AccountId::FromUserEmail(kUser2); |
| |
| TestSessionObserver observer; |
| SessionControllerImpl* controller = Shell::Get()->session_controller(); |
| controller->AddObserver(&observer); |
| |
| // Setup 2 users. |
| TestSessionControllerClient* session = GetSessionControllerClient(); |
| // Disable auto-provision of PrefService for each user. |
| constexpr bool kProvidePrefService = false; |
| session->AddUserSession(kUser1, user_manager::UserType::kRegular, |
| kProvidePrefService); |
| session->AddUserSession(kUser2, user_manager::UserType::kRegular, |
| kProvidePrefService); |
| |
| // The observer is not notified because the PrefService for kUser1 is not yet |
| // ready. |
| session->SwitchActiveUser(kUserAccount1); |
| EXPECT_EQ(nullptr, observer.last_user_pref_service()); |
| |
| auto pref_service = std::make_unique<TestingPrefServiceSimple>(); |
| RegisterUserProfilePrefs(pref_service->registry(), /*country=*/"", |
| /*for_test=*/true); |
| session->SetUserPrefService(kUserAccount1, std::move(pref_service)); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount1), |
| observer.last_user_pref_service()); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount1), |
| controller->GetLastActiveUserPrefService()); |
| |
| observer.clear_last_user_pref_service(); |
| |
| // Switching to a user for which prefs are not ready does not notify and |
| // GetLastActiveUserPrefService() returns the old PrefService. |
| session->SwitchActiveUser(kUserAccount2); |
| EXPECT_EQ(nullptr, observer.last_user_pref_service()); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount1), |
| controller->GetLastActiveUserPrefService()); |
| |
| session->SwitchActiveUser(kUserAccount1); |
| EXPECT_EQ(nullptr, observer.last_user_pref_service()); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount1), |
| controller->GetLastActiveUserPrefService()); |
| |
| // There should be no notification about a PrefService for an inactive user |
| // becoming initialized. |
| pref_service = std::make_unique<TestingPrefServiceSimple>(); |
| RegisterUserProfilePrefs(pref_service->registry(), /*country=*/"", |
| /*for_text=*/true); |
| session->SetUserPrefService(kUserAccount2, std::move(pref_service)); |
| EXPECT_EQ(nullptr, observer.last_user_pref_service()); |
| |
| session->SwitchActiveUser(kUserAccount2); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount2), |
| observer.last_user_pref_service()); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount2), |
| controller->GetLastActiveUserPrefService()); |
| |
| controller->RemoveObserver(&observer); |
| } |
| |
| // Verifies that SessionObserver is notified only once for the same user prefs. |
| TEST_F(SessionControllerImplPrefsTest, NotifyOnce) { |
| constexpr char kUser1[] = "user1@test.com"; |
| constexpr char kUser2[] = "user2@test.com"; |
| const AccountId kUserAccount1 = AccountId::FromUserEmail(kUser1); |
| const AccountId kUserAccount2 = AccountId::FromUserEmail(kUser2); |
| |
| TestSessionObserver observer; |
| SessionControllerImpl* controller = Shell::Get()->session_controller(); |
| controller->AddObserver(&observer); |
| ASSERT_EQ(0, observer.user_prefs_changed_count()); |
| |
| SimulateUserLogin(kUser1); |
| EXPECT_EQ(1, observer.user_prefs_changed_count()); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount1), |
| observer.last_user_pref_service()); |
| |
| SimulateUserLogin(kUser2); |
| EXPECT_EQ(2, observer.user_prefs_changed_count()); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount2), |
| observer.last_user_pref_service()); |
| |
| GetSessionControllerClient()->SwitchActiveUser(kUserAccount1); |
| EXPECT_EQ(3, observer.user_prefs_changed_count()); |
| EXPECT_EQ(controller->GetUserPrefServiceForUser(kUserAccount1), |
| observer.last_user_pref_service()); |
| |
| controller->RemoveObserver(&observer); |
| } |
| |
| // Base class for a session observer which can be mocked. |
| class MockSessionObserver : public SessionObserver { |
| public: |
| // SessionObserver: |
| MOCK_METHOD(void, OnActiveUserSessionChanged, (const AccountId&), (override)); |
| MOCK_METHOD(void, OnSessionStateChanged, (SessionState), (override)); |
| }; |
| |
| // Verifies that time of last session activation is stored to synced user prefs. |
| TEST_F(SessionControllerImplPrefsTest, SetsTimeOfLastSessionActivation) { |
| constexpr char kUser1Email[] = "user1@test.com"; |
| const AccountId kUser1AccountId = AccountId::FromUserEmail(kUser1Email); |
| constexpr char kUser2Email[] = "user2@test.com"; |
| const AccountId kUser2AccountId = AccountId::FromUserEmail(kUser2Email); |
| |
| // Register mock session observer. |
| testing::NiceMock<MockSessionObserver> mock_session_observer; |
| SessionControllerImpl* controller = Shell::Get()->session_controller(); |
| controller->AddObserver(&mock_session_observer); |
| |
| // Switch to test user. |
| TestSessionControllerClient* session = GetSessionControllerClient(); |
| session->AddUserSession(kUser1Email, user_manager::UserType::kRegular); |
| session->SwitchActiveUser(kUser1AccountId); |
| |
| // Initially time of last session activation is expected to be `base::Time()`. |
| base::Time expected_time_of_last_session_activation; |
| |
| // Iterate over all possible session states. |
| for (auto expected_session_state : std::vector<SessionState>{ |
| SessionState::OOBE, SessionState::LOGIN_PRIMARY, |
| SessionState::LOGGED_IN_NOT_ACTIVE, SessionState::ACTIVE, |
| SessionState::LOCKED, SessionState::LOGIN_SECONDARY, |
| SessionState::RMA}) { |
| // Set session state and expect observers to be notified of the event. |
| EXPECT_CALL(mock_session_observer, OnSessionStateChanged) |
| .WillOnce(testing::Invoke([&](SessionState session_state) { |
| EXPECT_EQ(session_state, expected_session_state); |
| |
| auto* const time_of_last_session_activation = |
| controller->GetUserPrefServiceForUser(kUser1AccountId) |
| ->FindPreference(prefs::kTimeOfLastSessionActivation); |
| |
| // Verify that the expected time of last session activation is stored. |
| // Note that it is intentional that even if the session is becoming |
| // activated, the stored time of last session activation will not be |
| // updated until *after* the session state changed event propagates. |
| // This is to allow observers to read the pref during event handling. |
| EXPECT_EQ( |
| *base::ValueToTime(time_of_last_session_activation->GetValue()), |
| expected_time_of_last_session_activation); |
| })); |
| session->SetSessionState(expected_session_state); |
| testing::Mock::VerifyAndClearExpectations(&mock_session_observer); |
| |
| { |
| // Flush message loop. |
| base::RunLoop run_loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| auto* const time_of_last_session_activation = |
| controller->GetUserPrefServiceForUser(kUser1AccountId) |
| ->FindPreference(prefs::kTimeOfLastSessionActivation); |
| |
| // When the session is activated, it is expected that the time of last |
| // session activation be stored to synced user prefs. Note that it is |
| // expected that this be done *after* notifying observers of the session |
| // state change in case observers read the pref during event handling. |
| if (controller->GetSessionState() == SessionState::ACTIVE) { |
| expected_time_of_last_session_activation = |
| *base::ValueToTime(time_of_last_session_activation->GetValue()); |
| |
| // It is expected that time of last session activation be rounded down to |
| // the nearest day since Windows epoch to reduce syncs. |
| EXPECT_EQ(base::Time::FromDeltaSinceWindowsEpoch(base::Days( |
| base::Time::Now().ToDeltaSinceWindowsEpoch().InDays())), |
| expected_time_of_last_session_activation); |
| continue; |
| } |
| |
| // When the session is not being activated, the stored time of last session |
| // activation should remain unchanged. |
| EXPECT_EQ(*base::ValueToTime(time_of_last_session_activation->GetValue()), |
| expected_time_of_last_session_activation); |
| } |
| |
| // Ensure session state is active so that we can confirm that switching the |
| // active user updates the time of last activation even if session state does |
| // not change. |
| session->SetSessionState(SessionState::ACTIVE); |
| |
| // Initially time of last session activation is expected to be `base::Time()`. |
| expected_time_of_last_session_activation = base::Time(); |
| |
| // Switch active user and expect observers to be notified of the event. |
| EXPECT_CALL(mock_session_observer, OnActiveUserSessionChanged) |
| .WillOnce(testing::Invoke([&](const AccountId& account_id) { |
| EXPECT_EQ(account_id, kUser2AccountId); |
| |
| auto* const time_of_last_session_activation = |
| controller->GetUserPrefServiceForUser(kUser2AccountId) |
| ->FindPreference(prefs::kTimeOfLastSessionActivation); |
| |
| // Verify that the expected time of last session activation is stored. |
| // Note that it is intentional the stored time of last session |
| // activation will not be updated until *after* the session state |
| // changed event propagates. This is to allow observers to read the pref |
| // during event handling. |
| EXPECT_EQ( |
| *base::ValueToTime(time_of_last_session_activation->GetValue()), |
| expected_time_of_last_session_activation); |
| })); |
| session->AddUserSession(kUser2Email, user_manager::UserType::kRegular); |
| session->SwitchActiveUser(kUser2AccountId); |
| testing::Mock::VerifyAndClearExpectations(&mock_session_observer); |
| |
| { |
| // Flush message loop. |
| base::RunLoop run_loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| auto* const time_of_last_session_activation = |
| controller->GetUserPrefServiceForUser(kUser2AccountId) |
| ->FindPreference(prefs::kTimeOfLastSessionActivation); |
| |
| // When switching to an active session, it is expected that the time of last |
| // session activation be stored to synced user prefs. Note that it is expected |
| // that this be done *after* notifying observers of the active user session |
| // change in case observers read the pref during event handling. |
| expected_time_of_last_session_activation = |
| *base::ValueToTime(time_of_last_session_activation->GetValue()); |
| |
| // It is expected that time of last session activation be rounded down to |
| // the nearest day since Windows epoch to reduce syncs. |
| EXPECT_EQ(base::Time::FromDeltaSinceWindowsEpoch(base::Days( |
| base::Time::Now().ToDeltaSinceWindowsEpoch().InDays())), |
| expected_time_of_last_session_activation); |
| |
| // Clean up. |
| controller->RemoveObserver(&mock_session_observer); |
| } |
| |
| TEST_F(SessionControllerImplTest, GetUserType) { |
| // Child accounts |
| UserSession session; |
| session.session_id = 1u; |
| session.user_info.type = user_manager::UserType::kChild; |
| controller()->UpdateUserSession(session); |
| EXPECT_EQ(user_manager::UserType::kChild, controller()->GetUserType()); |
| |
| // Regular accounts |
| session = UserSession(); |
| session.session_id = 1u; |
| session.user_info.type = user_manager::UserType::kRegular; |
| controller()->UpdateUserSession(session); |
| EXPECT_EQ(user_manager::UserType::kRegular, controller()->GetUserType()); |
| } |
| |
| TEST_F(SessionControllerImplTest, IsUserPrimary) { |
| controller()->ClearUserSessionsForTest(); |
| |
| // The first added user is a primary user |
| UserSession session; |
| session.session_id = 1u; |
| session.user_info.type = user_manager::UserType::kRegular; |
| controller()->UpdateUserSession(session); |
| EXPECT_TRUE(controller()->IsUserPrimary()); |
| |
| // The users added thereafter are not primary users |
| session = UserSession(); |
| session.session_id = 2u; |
| session.user_info.type = user_manager::UserType::kRegular; |
| controller()->UpdateUserSession(session); |
| // Simulates user switching by changing the order of session_ids. |
| controller()->SetUserSessionOrder({2u, 1u}); |
| EXPECT_FALSE(controller()->IsUserPrimary()); |
| } |
| |
| TEST_F(SessionControllerImplTest, IsUserFirstLogin) { |
| UserSession session; |
| session.session_id = 1u; |
| session.user_info.type = user_manager::UserType::kRegular; |
| controller()->UpdateUserSession(session); |
| EXPECT_FALSE(controller()->IsUserFirstLogin()); |
| |
| // user_info->is_new_profile being true means the user is first time login. |
| session = UserSession(); |
| session.session_id = 1u; |
| session.user_info.type = user_manager::UserType::kRegular; |
| session.user_info.is_new_profile = true; |
| controller()->UpdateUserSession(session); |
| EXPECT_TRUE(controller()->IsUserFirstLogin()); |
| } |
| |
| TEST_F(SessionControllerImplTest, ScopedScreenLockBlocker) { |
| SessionInfo info; |
| FillDefaultSessionInfo(&info); |
| SetSessionInfo(info); |
| UpdateSession(1u, "user1@test.com"); |
| EXPECT_TRUE(controller()->CanLockScreen()); |
| { |
| auto blocker1 = controller()->GetScopedScreenLockBlocker(); |
| EXPECT_FALSE(controller()->CanLockScreen()); |
| { |
| auto blocker2 = controller()->GetScopedScreenLockBlocker(); |
| EXPECT_FALSE(controller()->CanLockScreen()); |
| } |
| EXPECT_FALSE(controller()->CanLockScreen()); |
| } |
| EXPECT_TRUE(controller()->CanLockScreen()); |
| } |
| |
| class CanSwitchUserTest : public AshTestBase { |
| public: |
| // The action type to perform / check for upon user switching. |
| enum ActionType { |
| NO_DIALOG, // No dialog should be shown. |
| ACCEPT_DIALOG, // A dialog should be shown and we should accept it. |
| DECLINE_DIALOG, // A dialog should be shown and we do not accept it. |
| }; |
| CanSwitchUserTest() = default; |
| |
| CanSwitchUserTest(const CanSwitchUserTest&) = delete; |
| CanSwitchUserTest& operator=(const CanSwitchUserTest&) = delete; |
| |
| ~CanSwitchUserTest() override = default; |
| |
| void TearDown() override { |
| base::RunLoop().RunUntilIdle(); |
| AshTestBase::TearDown(); |
| } |
| |
| // Accessing the capture session functionality. |
| // Simulates a screen capture session start. |
| void StartCaptureSession() { |
| Shell::Get()->system_tray_notifier()->NotifyScreenAccessStart( |
| base::BindRepeating(&CanSwitchUserTest::StopCaptureCallback, |
| base::Unretained(this)), |
| base::RepeatingClosure(), std::u16string()); |
| } |
| |
| // The callback which gets called when the screen capture gets stopped. |
| void StopCaptureSession() { |
| Shell::Get()->system_tray_notifier()->NotifyScreenAccessStop(); |
| } |
| |
| // Simulates a screen capture session stop. |
| void StopCaptureCallback() { stop_capture_callback_hit_count_++; } |
| |
| // Accessing the share session functionality. |
| // Simulate a Screen share session start. |
| void StartShareSession() { |
| Shell::Get()->system_tray_notifier()->NotifyRemotingScreenShareStart( |
| base::BindRepeating(&CanSwitchUserTest::StopShareCallback, |
| base::Unretained(this))); |
| } |
| |
| // Simulates a screen share session stop. |
| void StopShareSession() { |
| Shell::Get()->system_tray_notifier()->NotifyRemotingScreenShareStop(); |
| } |
| |
| // The callback which gets called when the screen share gets stopped. |
| void StopShareCallback() { stop_share_callback_hit_count_++; } |
| |
| // Issuing a switch user call which might or might not create a dialog. |
| // The passed |action| type parameter defines the outcome (which will be |
| // checked) and the action the user will choose. |
| void SwitchUser(ActionType action) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&CloseMessageBox, action)); |
| Shell::Get()->session_controller()->CanSwitchActiveUser(base::BindOnce( |
| &CanSwitchUserTest::SwitchCallback, base::Unretained(this))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Called when the user will get actually switched. |
| void SwitchCallback(bool switch_user) { |
| if (switch_user) |
| switch_callback_hit_count_++; |
| } |
| |
| // Various counter accessors. |
| int stop_capture_callback_hit_count() const { |
| return stop_capture_callback_hit_count_; |
| } |
| int stop_share_callback_hit_count() const { |
| return stop_share_callback_hit_count_; |
| } |
| int switch_callback_hit_count() const { return switch_callback_hit_count_; } |
| |
| private: |
| static void CloseMessageBox(ActionType action) { |
| aura::Window* active_window = window_util::GetActiveWindow(); |
| views::DialogDelegate* dialog = |
| active_window ? views::Widget::GetWidgetForNativeWindow(active_window) |
| ->widget_delegate() |
| ->AsDialogDelegate() |
| : nullptr; |
| |
| switch (action) { |
| case NO_DIALOG: |
| EXPECT_FALSE(dialog); |
| return; |
| case ACCEPT_DIALOG: |
| ASSERT_TRUE(dialog); |
| EXPECT_TRUE(dialog->Accept()); |
| return; |
| case DECLINE_DIALOG: |
| ASSERT_TRUE(dialog); |
| EXPECT_TRUE(dialog->Close()); |
| return; |
| } |
| } |
| |
| // Various counters to query for. |
| int stop_capture_callback_hit_count_ = 0; |
| int stop_share_callback_hit_count_ = 0; |
| int switch_callback_hit_count_ = 0; |
| }; |
| |
| // Test that when there is no screen operation going on the user switch will be |
| // performed as planned. |
| TEST_F(CanSwitchUserTest, NoLock) { |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| SwitchUser(CanSwitchUserTest::NO_DIALOG); |
| EXPECT_EQ(1, switch_callback_hit_count()); |
| } |
| |
| // Test that with a screen capture operation going on, the user will need to |
| // confirm. Declining will neither change the running state or switch users. |
| TEST_F(CanSwitchUserTest, CaptureActiveDeclined) { |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| StartCaptureSession(); |
| SwitchUser(CanSwitchUserTest::DECLINE_DIALOG); |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| EXPECT_EQ(0, stop_capture_callback_hit_count()); |
| EXPECT_EQ(0, stop_share_callback_hit_count()); |
| StopCaptureSession(); |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| EXPECT_EQ(1, stop_capture_callback_hit_count()); |
| EXPECT_EQ(0, stop_share_callback_hit_count()); |
| } |
| |
| // Test that with a screen share operation going on, the user will need to |
| // confirm. Declining will neither change the running state or switch users. |
| TEST_F(CanSwitchUserTest, ShareActiveDeclined) { |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| StartShareSession(); |
| SwitchUser(CanSwitchUserTest::DECLINE_DIALOG); |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| EXPECT_EQ(0, stop_capture_callback_hit_count()); |
| EXPECT_EQ(0, stop_share_callback_hit_count()); |
| StopShareSession(); |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| EXPECT_EQ(0, stop_capture_callback_hit_count()); |
| EXPECT_EQ(1, stop_share_callback_hit_count()); |
| } |
| |
| // Test that with both operations going on, the user will need to confirm. |
| // Declining will neither change the running state or switch users. |
| TEST_F(CanSwitchUserTest, BothActiveDeclined) { |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| StartShareSession(); |
| StartCaptureSession(); |
| SwitchUser(CanSwitchUserTest::DECLINE_DIALOG); |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| EXPECT_EQ(0, stop_capture_callback_hit_count()); |
| EXPECT_EQ(0, stop_share_callback_hit_count()); |
| StopShareSession(); |
| StopCaptureSession(); |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| EXPECT_EQ(1, stop_capture_callback_hit_count()); |
| EXPECT_EQ(1, stop_share_callback_hit_count()); |
| } |
| |
| // Test that with a screen capture operation going on, the user will need to |
| // confirm. Accepting will change to stopped state and switch users. |
| TEST_F(CanSwitchUserTest, CaptureActiveAccepted) { |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| StartCaptureSession(); |
| SwitchUser(CanSwitchUserTest::ACCEPT_DIALOG); |
| EXPECT_EQ(1, switch_callback_hit_count()); |
| EXPECT_EQ(1, stop_capture_callback_hit_count()); |
| EXPECT_EQ(0, stop_share_callback_hit_count()); |
| // Another stop should have no effect. |
| StopCaptureSession(); |
| EXPECT_EQ(1, switch_callback_hit_count()); |
| EXPECT_EQ(1, stop_capture_callback_hit_count()); |
| EXPECT_EQ(0, stop_share_callback_hit_count()); |
| } |
| |
| // Test that with a screen share operation going on, the user will need to |
| // confirm. Accepting will change to stopped state and switch users. |
| TEST_F(CanSwitchUserTest, ShareActiveAccepted) { |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| StartShareSession(); |
| SwitchUser(CanSwitchUserTest::ACCEPT_DIALOG); |
| EXPECT_EQ(1, switch_callback_hit_count()); |
| EXPECT_EQ(0, stop_capture_callback_hit_count()); |
| EXPECT_EQ(1, stop_share_callback_hit_count()); |
| // Another stop should have no effect. |
| StopShareSession(); |
| EXPECT_EQ(1, switch_callback_hit_count()); |
| EXPECT_EQ(0, stop_capture_callback_hit_count()); |
| EXPECT_EQ(1, stop_share_callback_hit_count()); |
| } |
| |
| // Test that with both operations going on, the user will need to confirm. |
| // Accepting will change to stopped state and switch users. |
| TEST_F(CanSwitchUserTest, BothActiveAccepted) { |
| EXPECT_EQ(0, switch_callback_hit_count()); |
| StartShareSession(); |
| StartCaptureSession(); |
| SwitchUser(CanSwitchUserTest::ACCEPT_DIALOG); |
| EXPECT_EQ(1, switch_callback_hit_count()); |
| EXPECT_EQ(1, stop_capture_callback_hit_count()); |
| EXPECT_EQ(1, stop_share_callback_hit_count()); |
| // Another stop should have no effect. |
| StopShareSession(); |
| StopCaptureSession(); |
| EXPECT_EQ(1, switch_callback_hit_count()); |
| EXPECT_EQ(1, stop_capture_callback_hit_count()); |
| EXPECT_EQ(1, stop_share_callback_hit_count()); |
| } |
| |
| using SessionControllerImplUnblockTest = NoSessionAshTestBase; |
| |
| TEST_F(SessionControllerImplUnblockTest, ActiveWindowAfterUnblocking) { |
| EXPECT_TRUE(Shell::Get()->session_controller()->IsUserSessionBlocked()); |
| auto widget = CreateTestWidget(); |
| // |widget| should not be active as it is blocked by SessionControllerImpl. |
| EXPECT_FALSE(widget->IsActive()); |
| SimulateUserLogin("user@test.com"); |
| EXPECT_FALSE(Shell::Get()->session_controller()->IsUserSessionBlocked()); |
| |
| // |widget| should now be active as SessionControllerImpl no longer is |
| // blocking windows from becoming active. |
| EXPECT_TRUE(widget->IsActive()); |
| } |
| |
| TEST_F(SessionControllerImplWithShellTest, ExitFullscreenBeforeLock) { |
| CreateFullscreenWindow(); |
| EXPECT_TRUE(window_state_->IsFullscreen()); |
| |
| base::RunLoop run_loop; |
| Shell::Get()->session_controller()->PrepareForLock(run_loop.QuitClosure()); |
| |
| EXPECT_FALSE(window_state_->IsFullscreen()); |
| } |
| |
| } // namespace |
| } // namespace ash |