blob: 377e76b60cf15f4f35140f19a931ef34417de9ce [file] [log] [blame]
// Copyright 2016 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/session/session_controller.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "ash/metrics/user_metrics_recorder.h"
#include "ash/public/interfaces/pref_connector.mojom.h"
#include "ash/public/interfaces/user_info.mojom.h"
#include "ash/session/multiprofiles_intro_dialog.h"
#include "ash/session/session_aborted_dialog.h"
#include "ash/session/session_observer.h"
#include "ash/session/teleport_warning_dialog.h"
#include "ash/shell.h"
#include "ash/system/power/power_event_observer.h"
#include "ash/system/screen_security/screen_switch_check_controller.h"
#include "ash/wm/lock_state_controller.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/window_selector_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_event.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "chromeos/constants/chromeos_switches.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_type.h"
#include "services/preferences/public/cpp/pref_service_factory.h"
#include "services/preferences/public/mojom/preferences.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/message_center/message_center.h"
using session_manager::SessionState;
namespace ash {
namespace {
// Get the default session state. Default session state is ACTIVE when the
// process starts with a user session, i.e. the process has kLoginUser command
// line switch. This is needed because ash focus rules depends on whether
// session is blocked to pick an activatable window and chrome needs to create a
// focused browser window when starting with a user session (both in production
// and in tests). Using ACTIVE as default in this situation allows chrome to run
// without having to wait for session state to reach to ash. For other cases
// (oobe/login), there is only one login window. The login window always gets
// focus so default session state does not matter. Use UNKNOWN and wait for
// chrome to update ash for such cases.
SessionState GetDefaultSessionState() {
const bool start_with_user =
base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kLoginUser);
return start_with_user ? SessionState::ACTIVE : SessionState::UNKNOWN;
}
} // namespace
SessionController::SessionController(service_manager::Connector* connector)
: state_(GetDefaultSessionState()),
connector_(connector),
weak_ptr_factory_(this) {}
SessionController::~SessionController() {
// Abort pending start lock request.
if (!start_lock_callback_.is_null())
std::move(start_lock_callback_).Run(false /* locked */);
}
void SessionController::BindRequest(mojom::SessionControllerRequest request) {
bindings_.AddBinding(this, std::move(request));
}
int SessionController::NumberOfLoggedInUsers() const {
return static_cast<int>(user_sessions_.size());
}
AccountId SessionController::GetActiveAccountId() const {
return user_sessions_.empty() ? AccountId()
: user_sessions_[0]->user_info->account_id;
}
AddUserSessionPolicy SessionController::GetAddUserPolicy() const {
return add_user_session_policy_;
}
bool SessionController::IsActiveUserSessionStarted() const {
return !user_sessions_.empty();
}
bool SessionController::CanLockScreen() const {
return IsActiveUserSessionStarted() && can_lock_;
}
bool SessionController::IsScreenLocked() const {
return state_ == SessionState::LOCKED;
}
bool SessionController::ShouldLockScreenAutomatically() const {
return should_lock_screen_automatically_;
}
bool SessionController::IsRunningInAppMode() const {
return is_running_in_app_mode_;
}
bool SessionController::IsDemoSession() const {
return is_demo_session_;
}
bool SessionController::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 SessionController::IsInSecondaryLoginScreen() const {
return state_ == SessionState::LOGIN_SECONDARY;
}
SessionState SessionController::GetSessionState() const {
return state_;
}
bool SessionController::ShouldEnableSettings() const {
// Settings opens a web UI window, so it is not available at the lock screen.
if (!IsActiveUserSessionStarted() || IsScreenLocked() ||
IsInSecondaryLoginScreen()) {
return false;
}
return user_sessions_[0]->should_enable_settings;
}
bool SessionController::ShouldShowNotificationTray() const {
if (!IsActiveUserSessionStarted() || IsInSecondaryLoginScreen())
return false;
return user_sessions_[0]->should_show_notification_tray;
}
const std::vector<mojom::UserSessionPtr>& SessionController::GetUserSessions()
const {
return user_sessions_;
}
const mojom::UserSession* SessionController::GetUserSession(
UserIndex index) const {
if (index < 0 || index >= static_cast<UserIndex>(user_sessions_.size()))
return nullptr;
return user_sessions_[index].get();
}
const mojom::UserSession* SessionController::GetPrimaryUserSession() const {
auto it = std::find_if(user_sessions_.begin(), user_sessions_.end(),
[this](const mojom::UserSessionPtr& session) {
return session->session_id == primary_session_id_;
});
if (it == user_sessions_.end())
return nullptr;
return (*it).get();
}
bool SessionController::IsUserSupervised() const {
if (!IsActiveUserSessionStarted())
return false;
user_manager::UserType active_user_type = GetUserSession(0)->user_info->type;
return active_user_type == user_manager::USER_TYPE_SUPERVISED ||
active_user_type == user_manager::USER_TYPE_CHILD;
}
bool SessionController::IsUserLegacySupervised() const {
if (!IsActiveUserSessionStarted())
return false;
user_manager::UserType active_user_type = GetUserSession(0)->user_info->type;
return active_user_type == user_manager::USER_TYPE_SUPERVISED;
}
bool SessionController::IsUserChild() const {
if (!IsActiveUserSessionStarted())
return false;
user_manager::UserType active_user_type = GetUserSession(0)->user_info->type;
return active_user_type == user_manager::USER_TYPE_CHILD;
}
bool SessionController::IsUserPublicAccount() const {
if (!IsActiveUserSessionStarted())
return false;
user_manager::UserType active_user_type = GetUserSession(0)->user_info->type;
return active_user_type == user_manager::USER_TYPE_PUBLIC_ACCOUNT;
}
base::Optional<user_manager::UserType> SessionController::GetUserType() const {
if (!IsActiveUserSessionStarted())
return base::nullopt;
return base::make_optional(GetUserSession(0)->user_info->type);
}
bool SessionController::IsUserPrimary() const {
if (!IsActiveUserSessionStarted())
return false;
return GetUserSession(0)->session_id == primary_session_id_;
}
bool SessionController::IsUserFirstLogin() const {
if (!IsActiveUserSessionStarted())
return false;
return GetUserSession(0)->user_info->is_new_profile;
}
void SessionController::LockScreen() {
if (client_)
client_->RequestLockScreen();
}
void SessionController::RequestSignOut() {
if (client_)
client_->RequestSignOut();
}
void SessionController::SwitchActiveUser(const AccountId& account_id) {
if (client_)
client_->SwitchActiveUser(account_id);
}
void SessionController::CycleActiveUser(CycleUserDirection direction) {
if (client_)
client_->CycleActiveUser(direction);
}
void SessionController::ShowMultiProfileLogin() {
if (client_)
client_->ShowMultiProfileLogin();
}
PrefService* SessionController::GetSigninScreenPrefService() const {
return signin_screen_prefs_.get();
}
PrefService* SessionController::GetUserPrefServiceForUser(
const AccountId& account_id) const {
auto it = per_user_prefs_.find(account_id);
if (it != per_user_prefs_.end())
return it->second.get();
return nullptr;
}
PrefService* SessionController::GetPrimaryUserPrefService() const {
const mojom::UserSession* session = GetPrimaryUserSession();
return session ? GetUserPrefServiceForUser(session->user_info->account_id)
: nullptr;
}
PrefService* SessionController::GetLastActiveUserPrefService() const {
return last_active_user_prefs_;
}
PrefService* SessionController::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 signin_screen_prefs_.get();
}
void SessionController::AddObserver(SessionObserver* observer) {
observers_.AddObserver(observer);
}
void SessionController::RemoveObserver(SessionObserver* observer) {
observers_.RemoveObserver(observer);
}
void SessionController::SetClient(mojom::SessionControllerClientPtr client) {
client_ = std::move(client);
}
void SessionController::SetSessionInfo(mojom::SessionInfoPtr 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 SessionController::UpdateUserSession(mojom::UserSessionPtr user_session) {
auto it =
std::find_if(user_sessions_.begin(), user_sessions_.end(),
[&user_session](const mojom::UserSessionPtr& session) {
return session->session_id == user_session->session_id;
});
if (it == user_sessions_.end()) {
AddUserSession(std::move(user_session));
return;
}
*it = std::move(user_session);
for (auto& observer : observers_)
observer.OnUserSessionUpdated((*it)->user_info->account_id);
UpdateLoginStatus();
}
void SessionController::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<mojom::UserSessionPtr> sessions;
for (const auto& session_id : user_session_order) {
auto it =
std::find_if(user_sessions_.begin(), user_sessions_.end(),
[session_id](const mojom::UserSessionPtr& 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.
auto it = per_user_prefs_.find(user_sessions_[0]->user_info->account_id);
if (it != per_user_prefs_.end())
last_active_user_prefs_ = it->second.get();
for (auto& observer : observers_) {
observer.OnActiveUserSessionChanged(
user_sessions_[0]->user_info->account_id);
}
if (it != per_user_prefs_.end())
MaybeNotifyOnActiveUserPrefServiceChanged();
UpdateLoginStatus();
}
}
void SessionController::PrepareForLock(PrepareForLockCallback callback) {
// If the active window is fullscreen, exit fullscreen to avoid the web page
// or app mimicking the lock screen. Do not exit fullscreen if the shelf is
// visible while in fullscreen because the shelf makes it harder for a web
// page or app to mimick the lock screen.
wm::WindowState* active_window_state = wm::GetActiveWindowState();
if (active_window_state && active_window_state->IsFullscreen() &&
active_window_state->GetHideShelfWhenFullscreen()) {
const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN);
active_window_state->OnWMEvent(&event);
}
std::move(callback).Run();
}
void SessionController::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::Bind(&SessionController::OnLockAnimationFinished,
weak_ptr_factory_.GetWeakPtr()));
lock_state_controller->OnStartingLock();
}
void SessionController::NotifyChromeLockAnimationsComplete() {
Shell::Get()->power_event_observer()->OnLockAnimationsComplete();
}
void SessionController::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 SessionController::NotifyChromeTerminating() {
for (auto& observer : observers_)
observer.OnChromeTerminating();
}
void SessionController::SetSessionLengthLimit(base::TimeDelta length_limit,
base::TimeTicks start_time) {
session_length_limit_ = length_limit;
session_start_time_ = start_time;
for (auto& observer : observers_)
observer.OnSessionLengthLimitChanged();
}
void SessionController::CanSwitchActiveUser(
CanSwitchActiveUserCallback callback) {
// Cancel overview mode when switching user profiles.
WindowSelectorController* controller =
Shell::Get()->window_selector_controller();
if (controller->IsSelecting())
controller->ToggleOverview();
ash::Shell::Get()
->screen_switch_check_controller()
->CanSwitchAwayFromActiveUser(std::move(callback));
}
void SessionController::ShowMultiprofilesIntroDialog(
ShowMultiprofilesIntroDialogCallback callback) {
MultiprofilesIntroDialog::Show(std::move(callback));
}
void SessionController::ShowTeleportWarningDialog(
ShowTeleportWarningDialogCallback callback) {
TeleportWarningDialog::Show(std::move(callback));
}
void SessionController::ShowMultiprofilesSessionAbortedDialog(
const std::string& user_email) {
SessionAbortedDialog::Show(user_email);
}
void SessionController::AddSessionActivationObserverForAccountId(
const AccountId& account_id,
mojom::SessionActivationObserverPtr 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_.AddSessionActivationObserverForAccountId(
account_id, std::move(observer));
}
void SessionController::ClearUserSessionsForTest() {
user_sessions_.clear();
last_active_user_prefs_ = nullptr;
active_session_id_ = 0u;
primary_session_id_ = 0u;
}
void SessionController::FlushMojoForTest() {
client_.FlushForTesting();
}
void SessionController::LockScreenAndFlushForTest() {
LockScreen();
FlushMojoForTest();
}
void SessionController::SetSigninScreenPrefServiceForTest(
std::unique_ptr<PrefService> prefs) {
OnSigninScreenPrefServiceInitialized(std::move(prefs));
}
void SessionController::ProvideUserPrefServiceForTest(
const AccountId& account_id,
std::unique_ptr<PrefService> pref_service) {
OnProfilePrefServiceInitialized(account_id, std::move(pref_service));
}
void SessionController::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 SessionController::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_);
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);
}
// Request signin profile prefs only once.
if (!signin_screen_prefs_requested_) {
ConnectToSigninScreenPrefService();
signin_screen_prefs_requested_ = true;
}
if (was_user_session_blocked && !IsUserSessionBlocked())
EnsureActiveWindowAfterUnblockingUserSession();
}
void SessionController::AddUserSession(mojom::UserSessionPtr user_session) {
const AccountId account_id(user_session->user_info->account_id);
if (primary_session_id_ == 0u)
primary_session_id_ = user_session->session_id;
user_sessions_.push_back(std::move(user_session));
if (connector_) {
auto pref_registry = base::MakeRefCounted<PrefRegistrySimple>();
Shell::RegisterUserProfilePrefs(pref_registry.get());
ash::mojom::PrefConnectorPtr pref_connector_connector;
connector_->BindInterface(mojom::kPrefConnectorServiceName,
&pref_connector_connector);
prefs::mojom::PrefStoreConnectorPtr pref_connector;
pref_connector_connector->GetPrefStoreConnectorForUser(
account_id, mojo::MakeRequest(&pref_connector));
prefs::ConnectToPrefService(
std::move(pref_connector), std::move(pref_registry),
base::Bind(&SessionController::OnProfilePrefServiceInitialized,
weak_ptr_factory_.GetWeakPtr(), account_id));
}
UpdateLoginStatus();
for (auto& observer : observers_)
observer.OnUserSessionAdded(account_id);
}
LoginStatus SessionController::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:
return LoginStatus::NOT_LOGGED_IN;
case SessionState::ACTIVE:
return CalculateLoginStatusForActiveSession();
case SessionState::LOCKED:
return LoginStatus::LOCKED;
case SessionState::LOGIN_SECONDARY:
// TODO: There is no LoginStatus for this.
return LoginStatus::USER;
}
NOTREACHED();
return LoginStatus::NOT_LOGGED_IN;
}
LoginStatus SessionController::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::USER_TYPE_REGULAR:
return user_sessions_[0]->user_info->is_device_owner ? LoginStatus::OWNER
: LoginStatus::USER;
case user_manager::USER_TYPE_GUEST:
return LoginStatus::GUEST;
case user_manager::USER_TYPE_PUBLIC_ACCOUNT:
return LoginStatus::PUBLIC;
case user_manager::USER_TYPE_SUPERVISED:
return LoginStatus::SUPERVISED;
case user_manager::USER_TYPE_KIOSK_APP:
return LoginStatus::KIOSK_APP;
case user_manager::USER_TYPE_CHILD:
return LoginStatus::SUPERVISED;
case user_manager::USER_TYPE_ARC_KIOSK_APP:
return LoginStatus::ARC_KIOSK_APP;
case user_manager::USER_TYPE_ACTIVE_DIRECTORY:
// TODO: There is no LoginStatus for this.
return LoginStatus::USER;
case user_manager::NUM_USER_TYPES:
// Avoid having a "default" case so the compiler catches new enum values.
NOTREACHED();
return LoginStatus::USER;
}
NOTREACHED();
return LoginStatus::USER;
}
void SessionController::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 SessionController::OnLockAnimationFinished() {
if (!start_lock_callback_.is_null())
std::move(start_lock_callback_).Run(true /* locked */);
}
void SessionController::ConnectToSigninScreenPrefService() {
DCHECK(!signin_screen_prefs_requested_);
// Null in tests.
if (!connector_)
return;
// Connect to the PrefService for the signin profile.
auto pref_registry = base::MakeRefCounted<PrefRegistrySimple>();
Shell::RegisterSigninProfilePrefs(pref_registry.get());
ash::mojom::PrefConnectorPtr pref_connector_connector;
connector_->BindInterface(mojom::kPrefConnectorServiceName,
&pref_connector_connector);
prefs::mojom::PrefStoreConnectorPtr pref_connector;
pref_connector_connector->GetPrefStoreConnectorForSigninScreen(
mojo::MakeRequest(&pref_connector));
prefs::ConnectToPrefService(
std::move(pref_connector), std::move(pref_registry),
base::Bind(&SessionController::OnSigninScreenPrefServiceInitialized,
weak_ptr_factory_.GetWeakPtr()));
}
void SessionController::OnSigninScreenPrefServiceInitialized(
std::unique_ptr<PrefService> pref_service) {
// |pref_service| can be null when running standalone without chrome.
if (!pref_service)
return;
DCHECK(!signin_screen_prefs_);
signin_screen_prefs_ = std::move(pref_service);
for (auto& observer : observers_) {
observer.OnSigninScreenPrefServiceInitialized(signin_screen_prefs_.get());
}
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 SessionController::OnProfilePrefServiceInitialized(
const AccountId& account_id,
std::unique_ptr<PrefService> pref_service) {
// |pref_service| can be null when running standalone without chrome.
if (!pref_service)
return;
PrefService* pref_service_ptr = pref_service.get();
bool inserted =
per_user_prefs_.emplace(account_id, std::move(pref_service)).second;
DCHECK(inserted);
DCHECK(!user_sessions_.empty());
if (account_id == user_sessions_[0]->user_info->account_id) {
last_active_user_prefs_ = pref_service_ptr;
MaybeNotifyOnActiveUserPrefServiceChanged();
}
}
void SessionController::MaybeNotifyOnActiveUserPrefServiceChanged() {
DCHECK(last_active_user_prefs_);
if (!signin_screen_prefs_) {
// 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 SessionController::EnsureActiveWindowAfterUnblockingUserSession() {
// This happens only in tests (See SessionControllerTest).
if (!Shell::HasInstance())
return;
auto mru_list = Shell::Get()->mru_window_tracker()->BuildMruWindowList();
if (!mru_list.empty())
mru_list.front()->Focus();
}
} // namespace ash