blob: 1e12bb17edc42171f5c4977b42b434a30c7eca74 [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 "ash/session/session_observer.h"
#include "ash/shell.h"
#include "ash/system/power/power_event_observer.h"
#include "ash/wm/lock_state_controller.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "chromeos/chromeos_switches.h"
#include "components/signin/core/account_id/account_id.h"
#include "components/user_manager/user_type.h"
#include "services/service_manager/public/cpp/connector.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()
: state_(GetDefaultSessionState()), 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());
}
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::IsUserSessionBlocked() const {
// User sessions are blocked when session state is not ACTIVE, except that
// LOCKED state with a running unlocking animation. This is made an exception
// because the unlocking animation hides lock container at the end. During the
// unlock animation, IsUserSessionBlocked needs to return unblocked so that
// user windows are deemed activatable and ash correctly restore the active
// window before locking.
return state_ != SessionState::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();
}
bool SessionController::IsUserSupervised() const {
if (!IsActiveUserSessionStarted())
return false;
user_manager::UserType active_user_type = GetUserSession(0)->type;
return active_user_type == user_manager::USER_TYPE_SUPERVISED ||
active_user_type == user_manager::USER_TYPE_CHILD;
}
bool SessionController::IsUserChild() const {
if (!IsActiveUserSessionStarted())
return false;
user_manager::UserType active_user_type = GetUserSession(0)->type;
return active_user_type == user_manager::USER_TYPE_CHILD;
}
void SessionController::LockScreen() {
if (client_)
client_->RequestLockScreen();
}
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::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;
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)->account_id);
UpdateLoginStatus();
}
void SessionController::SetUserSessionOrder(
const std::vector<uint32_t>& user_session_order) {
DCHECK_EQ(user_sessions_.size(), user_session_order.size());
// 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_) {
active_session_id_ = user_sessions_[0]->session_id;
for (auto& observer : observers_)
observer.OnActiveUserSessionChanged(user_sessions_[0]->account_id);
UpdateLoginStatus();
}
}
void SessionController::StartLock(const StartLockCallback& callback) {
DCHECK(start_lock_callback_.is_null());
start_lock_callback_ = 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(
const RunUnlockAnimationCallback& callback) {
is_unlocking_ = true;
// Shell could have no instance in tests.
if (Shell::HasInstance())
Shell::Get()->lock_state_controller()->OnLockScreenHide(callback);
}
void SessionController::NotifyChromeTerminating() {
for (auto& observer : observers_)
observer.OnChromeTerminating();
}
void SessionController::ClearUserSessionsForTest() {
user_sessions_.clear();
}
void SessionController::FlushMojoForTest() {
client_.FlushForTesting();
}
void SessionController::LockScreenAndFlushForTest() {
LockScreen();
FlushMojoForTest();
}
void SessionController::SetSessionState(SessionState state) {
if (state_ == state)
return;
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);
}
}
void SessionController::AddUserSession(mojom::UserSessionPtr user_session) {
const AccountId account_id(user_session->account_id);
user_sessions_.push_back(std::move(user_session));
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]->type) {
case user_manager::USER_TYPE_REGULAR:
// TODO: This needs to distinguish between owner and non-owner.
return 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 */);
}
} // namespace ash