blob: 65f4f0b19c479de9d932634382f898df706966c1 [file] [log] [blame]
// 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 <optional>
#include <string>
#include <utility>
#include "ash/constants/ash_pref_names.h"
#include "ash/metrics/user_metrics_recorder.h"
#include "ash/public/cpp/session/scoped_screen_lock_blocker.h"
#include "ash/public/cpp/session/session_activation_observer.h"
#include "ash/public/cpp/session/session_controller_client.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/public/cpp/session/user_info.h"
#include "ash/session/fullscreen_controller.h"
#include "ash/session/multiprofiles_intro_dialog.h"
#include "ash/session/session_aborted_dialog.h"
#include "ash/session/teleport_warning_dialog.h"
#include "ash/shell.h"
#include "ash/system/power/power_event_observer.h"
#include "ash/system/privacy/screen_switch_check_controller.h"
#include "ash/wm/lock_state_controller.h"
#include "ash/wm/mru_window_tracker.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "components/account_id/account_id.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_type.h"
#include "ui/message_center/message_center.h"
using session_manager::SessionState;
namespace ash {
namespace {
void SetTimeOfLastSessionActivation(PrefService* user_pref_service) {
if (!user_pref_service) {
return;
}
// NOTE: Round down to the nearest day since Windows epoch to reduce syncs.
const base::Time time_of_last_session_activation =
base::Time::FromDeltaSinceWindowsEpoch(
base::Days(base::Time::Now().ToDeltaSinceWindowsEpoch().InDays()));
if (user_pref_service->GetTime(prefs::kTimeOfLastSessionActivation) !=
time_of_last_session_activation) {
user_pref_service->SetTime(prefs::kTimeOfLastSessionActivation,
time_of_last_session_activation);
}
}
} // namespace
class SessionControllerImpl::ScopedScreenLockBlockerImpl
: public ScopedScreenLockBlocker {
public:
explicit ScopedScreenLockBlockerImpl(
base::WeakPtr<SessionControllerImpl> session_controller)
: session_controller_(session_controller) {
DCHECK(session_controller_);
}
~ScopedScreenLockBlockerImpl() override {
if (session_controller_) {
session_controller_->RemoveScopedScreenLockBlocker();
}
}
private:
base::WeakPtr<SessionControllerImpl> session_controller_;
};
SessionControllerImpl::SessionControllerImpl()
: fullscreen_controller_(std::make_unique<FullscreenController>(this)) {}
SessionControllerImpl::~SessionControllerImpl() {
// Abort pending start lock request.
if (!start_lock_callback_.is_null())
std::move(start_lock_callback_).Run(false /* locked */);
}
// static
void SessionControllerImpl::RegisterUserProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterTimePref(
prefs::kTimeOfLastSessionActivation, base::Time(),
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
registry->RegisterTimePref(ash::prefs::kAshLoginSessionStartedTime,
base::Time());
registry->RegisterBooleanPref(
ash::prefs::kAshLoginSessionStartedIsFirstSession, false);
}
int SessionControllerImpl::NumberOfLoggedInUsers() const {
return static_cast<int>(user_sessions_.size());
}
AccountId SessionControllerImpl::GetActiveAccountId() const {
return user_sessions_.empty() ? AccountId()
: user_sessions_[0]->user_info.account_id;
}
AddUserSessionPolicy SessionControllerImpl::GetAddUserPolicy() const {
return add_user_session_policy_;
}
bool SessionControllerImpl::IsActiveAccountManaged() const {
CHECK(!user_sessions_.empty());
return user_sessions_[0]->user_info.is_managed;
}
bool SessionControllerImpl::IsActiveUserSessionStarted() const {
return !user_sessions_.empty();
}
bool SessionControllerImpl::CanLockScreen() const {
return scoped_screen_lock_blocker_count_ == 0 &&
IsActiveUserSessionStarted() && can_lock_;
}
bool SessionControllerImpl::ShouldLockScreenAutomatically() const {
return should_lock_screen_automatically_;
}
bool SessionControllerImpl::IsRunningInAppMode() const {
return is_running_in_app_mode_;
}
bool SessionControllerImpl::IsDemoSession() const {
return is_demo_session_;
}
bool SessionControllerImpl::IsUserSessionBlocked() const {
// User sessions are blocked when session state is not ACTIVE, with two
// exceptions:
// - LOGGED_IN_NOT_ACTIVE state. This is needed so that browser windows
// created by session restore (or a default new browser window) are properly
// activated before session state changes to ACTIVE.
// - LOCKED state with a running unlocking animation. This is needed because
// the unlocking animation hides the lock container at the end. During the
// unlock animation, IsUserSessionBlocked needs to return unblocked so that
// user windows are deemed activatable and ash correctly restores the active
// window before locking.
return state_ != SessionState::ACTIVE &&
state_ != SessionState::LOGGED_IN_NOT_ACTIVE &&
!(state_ == SessionState::LOCKED && is_unlocking_);
}
bool SessionControllerImpl::IsInSecondaryLoginScreen() const {
return state_ == SessionState::LOGIN_SECONDARY;
}
SessionState SessionControllerImpl::GetSessionState() const {
return state_;
}
bool SessionControllerImpl::ShouldEnableSettings() const {
// Settings opens a web UI window, so it is only available at active session
// at the moment.
return !IsUserSessionBlocked();
}
bool SessionControllerImpl::ShouldShowNotificationTray() const {
return IsActiveUserSessionStarted() && !IsInSecondaryLoginScreen();
}
const SessionControllerImpl::UserSessions&
SessionControllerImpl::GetUserSessions() const {
return user_sessions_;
}
const UserSession* SessionControllerImpl::GetUserSession(
UserIndex index) const {
if (index < 0 || index >= static_cast<UserIndex>(user_sessions_.size()))
return nullptr;
return user_sessions_[index].get();
}
const UserSession* SessionControllerImpl::GetUserSessionByAccountId(
const AccountId& account_id) const {
auto it = base::ranges::find(user_sessions_, account_id,
[](const std::unique_ptr<UserSession>& session) {
return session->user_info.account_id;
});
if (it == user_sessions_.end())
return nullptr;
return (*it).get();
}
const UserSession* SessionControllerImpl::GetPrimaryUserSession() const {
auto it = base::ranges::find(user_sessions_, primary_session_id_,
&UserSession::session_id);
if (it == user_sessions_.end())
return nullptr;
return (*it).get();
}
bool SessionControllerImpl::IsUserChild() const {
if (!IsActiveUserSessionStarted())
return false;
user_manager::UserType active_user_type = GetUserSession(0)->user_info.type;
return active_user_type == user_manager::UserType::kChild;
}
bool SessionControllerImpl::IsUserGuest() const {
if (!IsActiveUserSessionStarted()) {
return false;
}
user_manager::UserType active_user_type = GetUserSession(0)->user_info.type;
return active_user_type == user_manager::UserType::kGuest;
}
bool SessionControllerImpl::IsUserPublicAccount() const {
if (!IsActiveUserSessionStarted())
return false;
user_manager::UserType active_user_type = GetUserSession(0)->user_info.type;
return active_user_type == user_manager::UserType::kPublicAccount;
}
std::optional<user_manager::UserType> SessionControllerImpl::GetUserType()
const {
if (!IsActiveUserSessionStarted())
return std::nullopt;
return std::make_optional(GetUserSession(0)->user_info.type);
}
bool SessionControllerImpl::IsUserPrimary() const {
if (!IsActiveUserSessionStarted())
return false;
return GetUserSession(0)->session_id == primary_session_id_;
}
bool SessionControllerImpl::IsUserFirstLogin() const {
if (!IsActiveUserSessionStarted())
return false;
return GetUserSession(0)->user_info.is_new_profile;
}
bool SessionControllerImpl::IsEnterpriseManaged() const {
return client_ && client_->IsEnterpriseManaged();
}
std::optional<int> SessionControllerImpl::GetExistingUsersCount() const {
return client_ ? std::optional<int>(client_->GetExistingUsersCount())
: std::nullopt;
}
bool SessionControllerImpl::ShouldDisplayManagedUI() const {
if (!IsActiveUserSessionStarted())
return false;
return GetUserSession(0)->user_info.should_display_managed_ui;
}
void SessionControllerImpl::LockScreen() {
if (client_)
client_->RequestLockScreen();
}
void SessionControllerImpl::HideLockScreen() {
if (client_)
client_->RequestHideLockScreen();
}
void SessionControllerImpl::RequestSignOut() {
if (client_) {
client_->RequestSignOut();
}
}
void SessionControllerImpl::RequestRestartForUpdate() {
if (client_) {
client_->RequestRestartForUpdate();
}
}
void SessionControllerImpl::AttemptRestartChrome() {
if (client_)
client_->AttemptRestartChrome();
}
void SessionControllerImpl::SwitchActiveUser(const AccountId& account_id) {
if (client_)
client_->SwitchActiveUser(account_id);
}
void SessionControllerImpl::CycleActiveUser(CycleUserDirection direction) {
if (client_)
client_->CycleActiveUser(direction);
}
void SessionControllerImpl::ShowMultiProfileLogin() {
if (client_)
client_->ShowMultiProfileLogin();
}
PrefService* SessionControllerImpl::GetSigninScreenPrefService() const {
return client_ ? client_->GetSigninScreenPrefService() : nullptr;
}
PrefService* SessionControllerImpl::GetUserPrefServiceForUser(
const AccountId& account_id) const {
return client_ ? client_->GetUserPrefService(account_id) : nullptr;
}
base::FilePath SessionControllerImpl::GetProfilePath(
const AccountId& account_id) const {
return client_ ? client_->GetProfilePath(account_id) : base::FilePath();
}
PrefService* SessionControllerImpl::GetPrimaryUserPrefService() const {
const UserSession* session = GetPrimaryUserSession();
return session ? GetUserPrefServiceForUser(session->user_info.account_id)
: nullptr;
}
PrefService* SessionControllerImpl::GetLastActiveUserPrefService() const {
return last_active_user_prefs_;
}
PrefService* SessionControllerImpl::GetActivePrefService() const {
// Use the active user prefs once they become available. Check the PrefService
// object instead of session state because prefs load is async after login.
if (last_active_user_prefs_)
return last_active_user_prefs_;
return GetSigninScreenPrefService();
}
std::unique_ptr<ScopedScreenLockBlocker>
SessionControllerImpl::GetScopedScreenLockBlocker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
++scoped_screen_lock_blocker_count_;
return std::make_unique<SessionControllerImpl::ScopedScreenLockBlockerImpl>(
weak_ptr_factory_.GetWeakPtr());
}
void SessionControllerImpl::AddObserver(SessionObserver* observer) {
observers_.AddObserver(observer);
}
void SessionControllerImpl::RemoveObserver(SessionObserver* observer) {
observers_.RemoveObserver(observer);
}
bool SessionControllerImpl::IsScreenLocked() const {
return state_ == SessionState::LOCKED;
}
void SessionControllerImpl::SetClient(SessionControllerClient* client) {
client_ = client;
}
void SessionControllerImpl::SetSessionInfo(const SessionInfo& info) {
can_lock_ = info.can_lock_screen;
should_lock_screen_automatically_ = info.should_lock_screen_automatically;
is_running_in_app_mode_ = info.is_running_in_app_mode;
if (info.is_demo_session)
SetIsDemoSession();
add_user_session_policy_ = info.add_user_session_policy;
SetSessionState(info.state);
}
void SessionControllerImpl::UpdateUserSession(const UserSession& user_session) {
auto it = base::ranges::find(user_sessions_, user_session.session_id,
&UserSession::session_id);
if (it == user_sessions_.end()) {
AddUserSession(user_session);
return;
}
*it = std::make_unique<UserSession>(user_session);
for (auto& observer : observers_)
observer.OnUserSessionUpdated((*it)->user_info.account_id);
UpdateLoginStatus();
}
void SessionControllerImpl::SetUserSessionOrder(
const std::vector<uint32_t>& user_session_order) {
DCHECK_EQ(user_sessions_.size(), user_session_order.size());
AccountId last_active_account_id;
if (user_sessions_.size())
last_active_account_id = user_sessions_[0]->user_info.account_id;
// Adjusts |user_sessions_| to match the given order.
std::vector<std::unique_ptr<UserSession>> sessions;
for (const auto& session_id : user_session_order) {
auto it = base::ranges::find_if(
user_sessions_,
[session_id](const std::unique_ptr<UserSession>& session) {
return session && session->session_id == session_id;
});
if (it == user_sessions_.end()) {
LOG(ERROR) << "Unknown session id =" << session_id;
continue;
}
sessions.push_back(std::move(*it));
}
user_sessions_.swap(sessions);
// Check active user change and notifies observers.
if (user_sessions_[0]->session_id != active_session_id_) {
const bool is_first_session = active_session_id_ == 0u;
active_session_id_ = user_sessions_[0]->session_id;
if (is_first_session) {
for (auto& observer : observers_)
observer.OnFirstSessionStarted();
}
session_activation_observer_holder_.NotifyActiveSessionChanged(
last_active_account_id, user_sessions_[0]->user_info.account_id);
// When switching to a user for whose PrefService is not ready,
// |last_active_user_prefs_| continues to point to the PrefService of the
// most-recently active user with a loaded PrefService.
PrefService* user_pref_service =
GetUserPrefServiceForUser(user_sessions_[0]->user_info.account_id);
if (user_pref_service && last_active_user_prefs_ != user_pref_service) {
last_active_user_prefs_ = user_pref_service;
MaybeNotifyOnActiveUserPrefServiceChanged();
}
for (auto& observer : observers_) {
observer.OnActiveUserSessionChanged(
user_sessions_[0]->user_info.account_id);
}
// NOTE: This pref is intentionally set *after* notifying observers of
// active user session changes so observers can use time of last activation
// during event handling.
if (state_ == SessionState::ACTIVE) {
SetTimeOfLastSessionActivation(user_pref_service);
}
UpdateLoginStatus();
}
}
void SessionControllerImpl::PrepareForLock(PrepareForLockCallback callback) {
fullscreen_controller_->MaybeExitFullscreenBeforeLock(std::move(callback));
}
void SessionControllerImpl::StartLock(StartLockCallback callback) {
DCHECK(start_lock_callback_.is_null());
start_lock_callback_ = std::move(callback);
LockStateController* const lock_state_controller =
Shell::Get()->lock_state_controller();
lock_state_controller->SetLockScreenDisplayedCallback(
base::BindOnce(&SessionControllerImpl::OnLockAnimationFinished,
weak_ptr_factory_.GetWeakPtr()));
}
void SessionControllerImpl::NotifyChromeLockAnimationsComplete() {
Shell::Get()->power_event_observer()->OnLockAnimationsComplete();
}
void SessionControllerImpl::RunUnlockAnimation(
RunUnlockAnimationCallback callback) {
is_unlocking_ = true;
// Shell could have no instance in tests.
if (Shell::HasInstance())
Shell::Get()->lock_state_controller()->OnLockScreenHide(
std::move(callback));
}
void SessionControllerImpl::NotifyChromeTerminating() {
for (auto& observer : observers_)
observer.OnChromeTerminating();
}
void SessionControllerImpl::SetSessionLengthLimit(base::TimeDelta length_limit,
base::Time start_time) {
session_length_limit_ = length_limit;
session_start_time_ = start_time;
for (auto& observer : observers_)
observer.OnSessionLengthLimitChanged();
}
void SessionControllerImpl::CanSwitchActiveUser(
CanSwitchActiveUserCallback callback) {
Shell::Get()->screen_switch_check_controller()->CanSwitchAwayFromActiveUser(
std::move(callback));
}
void SessionControllerImpl::ShowMultiprofilesIntroDialog(
ShowMultiprofilesIntroDialogCallback callback) {
MultiprofilesIntroDialog::Show(std::move(callback));
}
void SessionControllerImpl::ShowTeleportWarningDialog(
ShowTeleportWarningDialogCallback callback) {
TeleportWarningDialog::Show(std::move(callback));
}
void SessionControllerImpl::ShowMultiprofilesSessionAbortedDialog(
const std::string& user_email) {
SessionAbortedDialog::Show(user_email);
}
void SessionControllerImpl::AddSessionActivationObserverForAccountId(
const AccountId& account_id,
SessionActivationObserver* observer) {
bool locked = state_ == SessionState::LOCKED;
observer->OnLockStateChanged(locked);
observer->OnSessionActivated(user_sessions_.size() &&
user_sessions_[0]->user_info.account_id ==
account_id);
session_activation_observer_holder_.AddForAccountId(account_id, observer);
}
void SessionControllerImpl::RemoveSessionActivationObserverForAccountId(
const AccountId& account_id,
SessionActivationObserver* observer) {
session_activation_observer_holder_.RemoveForAccountId(account_id, observer);
}
void SessionControllerImpl::ClearUserSessionsForTest() {
user_sessions_.clear();
last_active_user_prefs_ = nullptr;
active_session_id_ = 0u;
primary_session_id_ = 0u;
}
void SessionControllerImpl::SetIsDemoSession() {
if (is_demo_session_)
return;
is_demo_session_ = true;
Shell::Get()->metrics()->StartDemoSessionMetricsRecording();
// Notifications should be silenced during demo sessions.
message_center::MessageCenter::Get()->SetQuietMode(true);
}
void SessionControllerImpl::SetSessionState(SessionState state) {
if (state_ == state)
return;
const bool was_user_session_blocked = IsUserSessionBlocked();
const bool was_locked = state_ == SessionState::LOCKED;
state_ = state;
for (auto& observer : observers_)
observer.OnSessionStateChanged(state_);
// NOTE: This pref is intentionally set *after* notifying observers of state
// changes so observers can use time of last activation during event handling.
if (state_ == SessionState::ACTIVE) {
SetTimeOfLastSessionActivation(
GetUserPrefServiceForUser(GetActiveAccountId()));
}
UpdateLoginStatus();
const bool locked = state_ == SessionState::LOCKED;
if (was_locked != locked) {
if (!locked)
is_unlocking_ = false;
for (auto& observer : observers_)
observer.OnLockStateChanged(locked);
session_activation_observer_holder_.NotifyLockStateChanged(locked);
}
EnsureSigninScreenPrefService();
if (was_user_session_blocked && !IsUserSessionBlocked())
EnsureActiveWindowAfterUnblockingUserSession();
}
void SessionControllerImpl::AddUserSession(const UserSession& user_session) {
if (primary_session_id_ == 0u)
primary_session_id_ = user_session.session_id;
user_sessions_.push_back(std::make_unique<UserSession>(user_session));
const AccountId account_id(user_session.user_info.account_id);
PrefService* user_prefs = GetUserPrefServiceForUser(account_id);
// |user_prefs| could be null in tests.
if (user_prefs)
OnProfilePrefServiceInitialized(account_id, user_prefs);
UpdateLoginStatus();
for (auto& observer : observers_)
observer.OnUserSessionAdded(account_id);
}
LoginStatus SessionControllerImpl::CalculateLoginStatus() const {
// TODO(jamescook|xiyuan): There is not a 1:1 mapping of SessionState to
// LoginStatus. Fix the cases that don't match. http://crbug.com/701193
switch (state_) {
case SessionState::UNKNOWN:
case SessionState::OOBE:
case SessionState::LOGIN_PRIMARY:
case SessionState::LOGGED_IN_NOT_ACTIVE:
case SessionState::RMA:
return LoginStatus::NOT_LOGGED_IN;
case SessionState::ACTIVE:
return CalculateLoginStatusForActiveSession();
case SessionState::LOCKED:
return LoginStatus::LOCKED;
case SessionState::LOGIN_SECONDARY:
// TODO(jamescook): There is no LoginStatus for this.
return LoginStatus::USER;
}
NOTREACHED();
return LoginStatus::NOT_LOGGED_IN;
}
LoginStatus SessionControllerImpl::CalculateLoginStatusForActiveSession()
const {
DCHECK(state_ == SessionState::ACTIVE);
if (user_sessions_.empty()) // Can be empty in tests.
return LoginStatus::USER;
switch (user_sessions_[0]->user_info.type) {
case user_manager::UserType::kRegular:
return LoginStatus::USER;
case user_manager::UserType::kGuest:
return LoginStatus::GUEST;
case user_manager::UserType::kPublicAccount:
return LoginStatus::PUBLIC;
case user_manager::UserType::kKioskApp:
return LoginStatus::KIOSK_APP;
case user_manager::UserType::kChild:
return LoginStatus::CHILD;
case user_manager::UserType::kArcKioskApp:
return LoginStatus::KIOSK_APP;
case user_manager::UserType::kWebKioskApp:
return LoginStatus::KIOSK_APP;
}
NOTREACHED();
return LoginStatus::USER;
}
void SessionControllerImpl::UpdateLoginStatus() {
const LoginStatus new_login_status = CalculateLoginStatus();
if (new_login_status == login_status_)
return;
login_status_ = new_login_status;
for (auto& observer : observers_)
observer.OnLoginStatusChanged(login_status_);
}
void SessionControllerImpl::OnLockAnimationFinished() {
if (!start_lock_callback_.is_null())
std::move(start_lock_callback_).Run(true /* locked */);
}
void SessionControllerImpl::EnsureSigninScreenPrefService() {
// Obtain and notify signin profile prefs only once.
if (signin_screen_prefs_obtained_)
return;
PrefService* const signin_prefs = GetSigninScreenPrefService();
if (!signin_prefs)
return;
OnSigninScreenPrefServiceInitialized(signin_prefs);
}
void SessionControllerImpl::OnSigninScreenPrefServiceInitialized(
PrefService* pref_service) {
DCHECK(pref_service);
DCHECK(!signin_screen_prefs_obtained_);
signin_screen_prefs_obtained_ = true;
for (auto& observer : observers_)
observer.OnSigninScreenPrefServiceInitialized(pref_service);
if (on_active_user_prefs_changed_notify_deferred_) {
// Notify obsevers with the deferred OnActiveUserPrefServiceChanged(). Do
// this in a separate loop from the above since observers might depend on
// each other and we want to avoid having inconsistent states.
for (auto& observer : observers_)
observer.OnActiveUserPrefServiceChanged(last_active_user_prefs_);
on_active_user_prefs_changed_notify_deferred_ = false;
}
}
void SessionControllerImpl::OnProfilePrefServiceInitialized(
const AccountId& account_id,
PrefService* pref_service) {
// |pref_service| can be null in tests.
if (!pref_service)
return;
DCHECK(!user_sessions_.empty());
if (account_id == user_sessions_[0]->user_info.account_id) {
last_active_user_prefs_ = pref_service;
MaybeNotifyOnActiveUserPrefServiceChanged();
}
}
void SessionControllerImpl::MaybeNotifyOnActiveUserPrefServiceChanged() {
DCHECK(last_active_user_prefs_);
if (!signin_screen_prefs_obtained_) {
// We must guarantee that OnSigninScreenPrefServiceInitialized() is called
// before OnActiveUserPrefServiceChanged(), so defer notifying the
// observers until the sign in prefs are received.
on_active_user_prefs_changed_notify_deferred_ = true;
return;
}
for (auto& observer : observers_)
observer.OnActiveUserPrefServiceChanged(last_active_user_prefs_);
}
void SessionControllerImpl::EnsureActiveWindowAfterUnblockingUserSession() {
// This happens only in tests (See SessionControllerImplTest).
if (!Shell::HasInstance())
return;
auto mru_list =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
if (!mru_list.empty())
mru_list.front()->Focus();
}
void SessionControllerImpl::RemoveScopedScreenLockBlocker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(scoped_screen_lock_blocker_count_, 0);
--scoped_screen_lock_blocker_count_;
}
} // namespace ash